/* $OpenBSD: interrupt.c,v 1.19 2024/06/26 01:40:49 jsg Exp $ */ /* $NetBSD: interrupt.c,v 1.18 2006/01/25 00:02:57 uwe Exp $ */ /*- * Copyright (c) 2002 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by UCHIYAMA Yasushi. * * 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 #include #include #include /* uvmexp.intrs */ #include #include #include #include #include #include void intc_intr_priority(int, int); struct intc_intrhand *intc_alloc_ih(void); void intc_free_ih(struct intc_intrhand *); int intc_unknown_intr(void *); #ifdef SH4 void intpri_intr_enable(int); void intpri_intr_disable(int); #endif void tmu1_oneshot(void); int tmu1_intr(void *); void setsoft(int); /* * EVTCODE to intc_intrhand mapper. * max #76 is SH4_INTEVT_TMU4 (0xb80) */ int8_t __intc_evtcode_to_ih[128]; struct intc_intrhand __intc_intrhand[_INTR_N + 1] = { /* Place holder interrupt handler for unregistered interrupt. */ [0] = { .ih_func = intc_unknown_intr, .ih_level = 0xf0 } }; struct sh_soft_intr sh_soft_intrs[_IPL_NSOFT]; /* * SH INTC support. */ void intc_init(void) { switch (cpu_product) { #ifdef SH3 case CPU_PRODUCT_7709: case CPU_PRODUCT_7709A: _reg_write_2(SH7709_IPRC, 0); _reg_write_2(SH7709_IPRD, 0); _reg_write_2(SH7709_IPRE, 0); /* FALLTHROUGH */ case CPU_PRODUCT_7708: case CPU_PRODUCT_7708S: case CPU_PRODUCT_7708R: _reg_write_2(SH3_IPRA, 0); _reg_write_2(SH3_IPRB, 0); break; #endif /* SH3 */ #ifdef SH4 case CPU_PRODUCT_7751: case CPU_PRODUCT_7751R: _reg_write_4(SH4_INTPRI00, 0); _reg_write_4(SH4_INTMSK00, INTMSK00_MASK_ALL); /* FALLTHROUGH */ case CPU_PRODUCT_7750S: case CPU_PRODUCT_7750R: _reg_write_2(SH4_IPRD, 0); /* FALLTHROUGH */ case CPU_PRODUCT_7750: _reg_write_2(SH4_IPRA, 0); _reg_write_2(SH4_IPRB, 0); _reg_write_2(SH4_IPRC, 0); break; #endif /* SH4 */ } } void * intc_intr_establish(int evtcode, int trigger, int level, int (*ih_func)(void *), void *ih_arg, const char *name) { struct intc_intrhand *ih; KDASSERT(evtcode >= 0x200 && level > 0); ih = intc_alloc_ih(); ih->ih_func = ih_func; ih->ih_arg = ih_arg; ih->ih_level = level << 4; /* convert to SR.IMASK format. */ ih->ih_evtcode = evtcode; ih->ih_irq = evtcode >> 5; ih->ih_name = name; if (name) evcount_attach(&ih->ih_count, name, &ih->ih_irq); /* Map interrupt handler */ EVTCODE_TO_IH_INDEX(evtcode) = ih->ih_idx; /* Priority */ intc_intr_priority(evtcode, level); /* Sense select (SH7709, SH7709A only) XXX notyet */ return (ih); } void intc_intr_disestablish(void *arg) { struct intc_intrhand *ih = arg; int evtcode = ih->ih_evtcode; /* Mask interrupt if IPR can manage it. if not, cascaded ICU will do */ intc_intr_priority(evtcode, 0); /* Unmap interrupt handler */ EVTCODE_TO_IH_INDEX(evtcode) = 0; if (ih->ih_name) evcount_detach(&ih->ih_count); intc_free_ih(ih); } void intc_intr_disable(int evtcode) { int s; s = _cpu_intr_suspend(); KASSERT(EVTCODE_TO_IH_INDEX(evtcode) != 0); /* there is a handler */ switch (evtcode) { default: intc_intr_priority(evtcode, 0); break; #ifdef SH4 case SH4_INTEVT_PCISERR: case SH4_INTEVT_PCIDMA3: case SH4_INTEVT_PCIDMA2: case SH4_INTEVT_PCIDMA1: case SH4_INTEVT_PCIDMA0: case SH4_INTEVT_PCIPWON: case SH4_INTEVT_PCIPWDWN: case SH4_INTEVT_PCIERR: intpri_intr_disable(evtcode); break; #endif } _cpu_intr_resume(s); } void intc_intr_enable(int evtcode) { struct intc_intrhand *ih; int s; s = _cpu_intr_suspend(); KASSERT(EVTCODE_TO_IH_INDEX(evtcode) != 0); /* there is a handler */ switch (evtcode) { default: ih = EVTCODE_IH(evtcode); /* ih_level is in the SR.IMASK format */ intc_intr_priority(evtcode, (ih->ih_level >> 4)); break; #ifdef SH4 case SH4_INTEVT_PCISERR: case SH4_INTEVT_PCIDMA3: case SH4_INTEVT_PCIDMA2: case SH4_INTEVT_PCIDMA1: case SH4_INTEVT_PCIDMA0: case SH4_INTEVT_PCIPWON: case SH4_INTEVT_PCIPWDWN: case SH4_INTEVT_PCIERR: intpri_intr_enable(evtcode); break; #endif } _cpu_intr_resume(s); } /* * int intc_intr_priority(int evtcode, int level) * Setup interrupt priority register. * SH7708, SH7708S, SH7708R, SH7750, SH7750S ... evtcode is INTEVT * SH7709, SH7709A ... evtcode is INTEVT2 */ void intc_intr_priority(int evtcode, int level) { volatile uint16_t *iprreg; int pos; uint16_t r; #define __SH_IPR(_sh, _ipr, _pos) \ do { \ iprreg = (volatile uint16_t *)(SH ## _sh ## _IPR ## _ipr); \ pos = (_pos); \ } while (/*CONSTCOND*/0) #define SH3_IPR(_ipr, _pos) __SH_IPR(3, _ipr, _pos) #define SH4_IPR(_ipr, _pos) __SH_IPR(4, _ipr, _pos) #define SH7709_IPR(_ipr, _pos) __SH_IPR(7709, _ipr, _pos) #define SH_IPR(_ipr, _pos) \ do { \ if (CPU_IS_SH3) \ SH3_IPR(_ipr, _pos); \ else \ SH4_IPR(_ipr, _pos); \ } while (/*CONSTCOND*/0) iprreg = 0; pos = -1; switch (evtcode) { case SH_INTEVT_TMU0_TUNI0: SH_IPR(A, 12); break; case SH_INTEVT_TMU1_TUNI1: SH_IPR(A, 8); break; case SH_INTEVT_TMU2_TUNI2: SH_IPR(A, 4); break; case SH_INTEVT_WDT_ITI: SH_IPR(B, 12); break; case SH_INTEVT_SCI_ERI: case SH_INTEVT_SCI_RXI: case SH_INTEVT_SCI_TXI: case SH_INTEVT_SCI_TEI: SH_IPR(B, 4); break; } #ifdef SH3 if (CPU_IS_SH3) { switch (evtcode) { case SH7709_INTEVT2_IRQ3: SH7709_IPR(C, 12); break; case SH7709_INTEVT2_IRQ2: SH7709_IPR(C, 8); break; case SH7709_INTEVT2_IRQ1: SH7709_IPR(C, 4); break; case SH7709_INTEVT2_IRQ0: SH7709_IPR(C, 0); break; case SH7709_INTEVT2_PINT07: SH7709_IPR(D, 12); break; case SH7709_INTEVT2_PINT8F: SH7709_IPR(D, 8); break; case SH7709_INTEVT2_IRQ5: SH7709_IPR(D, 4); break; case SH7709_INTEVT2_IRQ4: SH7709_IPR(D, 0); break; case SH7709_INTEVT2_DEI0: case SH7709_INTEVT2_DEI1: case SH7709_INTEVT2_DEI2: case SH7709_INTEVT2_DEI3: SH7709_IPR(E, 12); break; case SH7709_INTEVT2_IRDA_ERI: case SH7709_INTEVT2_IRDA_RXI: case SH7709_INTEVT2_IRDA_BRI: case SH7709_INTEVT2_IRDA_TXI: SH7709_IPR(E, 8); break; case SH7709_INTEVT2_SCIF_ERI: case SH7709_INTEVT2_SCIF_RXI: case SH7709_INTEVT2_SCIF_BRI: case SH7709_INTEVT2_SCIF_TXI: SH7709_IPR(E, 4); break; case SH7709_INTEVT2_ADC: SH7709_IPR(E, 0); break; } } #endif /* SH3 */ #ifdef SH4 if (CPU_IS_SH4) { switch (evtcode) { case SH4_INTEVT_SCIF_ERI: case SH4_INTEVT_SCIF_RXI: case SH4_INTEVT_SCIF_BRI: case SH4_INTEVT_SCIF_TXI: SH4_IPR(C, 4); break; #if 0 case SH4_INTEVT_PCISERR: case SH4_INTEVT_PCIDMA3: case SH4_INTEVT_PCIDMA2: case SH4_INTEVT_PCIDMA1: case SH4_INTEVT_PCIDMA0: case SH4_INTEVT_PCIPWON: case SH4_INTEVT_PCIPWDWN: case SH4_INTEVT_PCIERR: #endif case SH4_INTEVT_TMU3: case SH4_INTEVT_TMU4: intpri_intr_priority(evtcode, level); break; } } #endif /* SH4 */ /* * XXX: This function gets called even for interrupts that * don't have their priority defined by IPR registers. */ if (pos < 0) return; r = _reg_read_2(iprreg); r = (r & ~(0xf << (pos))) | (level << (pos)); _reg_write_2(iprreg, r); } /* * Interrupt handler holder allocater. */ struct intc_intrhand * intc_alloc_ih(void) { /* #0 is reserved for unregistered interrupt. */ struct intc_intrhand *ih = &__intc_intrhand[1]; int i; for (i = 1; i <= _INTR_N; i++, ih++) if (ih->ih_idx == 0) { /* no driver uses this. */ ih->ih_idx = i; /* register myself */ return (ih); } panic("increase _INTR_N greater than %d", _INTR_N); return (NULL); } void intc_free_ih(struct intc_intrhand *ih) { ih->ih_idx = 0; memset(ih, 0, sizeof(*ih)); } /* Place-holder for debugging */ int intc_unknown_intr(void *arg) { printf("INTEVT=0x%x", _reg_read_4(SH_(INTEVT))); if (cpu_product == CPU_PRODUCT_7709 || cpu_product == CPU_PRODUCT_7709A) printf(" INTEVT2=0x%x", _reg_read_4(SH7709_INTEVT2)); printf("\n"); panic("unknown interrupt"); /* NOTREACHED */ return (0); } #ifdef SH4 /* SH7751 support */ /* * INTPRIxx */ void intpri_intr_priority(int evtcode, int level) { volatile uint32_t *iprreg; uint32_t r; int pos; if (!CPU_IS_SH4) return; switch (cpu_product) { default: return; case CPU_PRODUCT_7751: case CPU_PRODUCT_7751R: break; } iprreg = (volatile uint32_t *)SH4_INTPRI00; pos = -1; switch (evtcode) { case SH4_INTEVT_PCIDMA3: case SH4_INTEVT_PCIDMA2: case SH4_INTEVT_PCIDMA1: case SH4_INTEVT_PCIDMA0: case SH4_INTEVT_PCIPWDWN: case SH4_INTEVT_PCIPWON: case SH4_INTEVT_PCIERR: pos = 0; break; case SH4_INTEVT_PCISERR: pos = 4; break; case SH4_INTEVT_TMU3: pos = 8; break; case SH4_INTEVT_TMU4: pos = 12; break; } if (pos < 0) { return; } r = _reg_read_4(iprreg); r = (r & ~(0xf << pos)) | (level << pos); _reg_write_4(iprreg, r); } void intpri_intr_enable(int evtcode) { volatile uint32_t *iprreg; uint32_t bit; if (!CPU_IS_SH4) return; switch (cpu_product) { default: return; case CPU_PRODUCT_7751: case CPU_PRODUCT_7751R: break; } iprreg = (volatile uint32_t *)SH4_INTMSKCLR00; bit = 0; switch (evtcode) { case SH4_INTEVT_PCISERR: case SH4_INTEVT_PCIDMA3: case SH4_INTEVT_PCIDMA2: case SH4_INTEVT_PCIDMA1: case SH4_INTEVT_PCIDMA0: case SH4_INTEVT_PCIPWON: case SH4_INTEVT_PCIPWDWN: case SH4_INTEVT_PCIERR: bit = (1 << ((evtcode - SH4_INTEVT_PCISERR) >> 5)); break; case SH4_INTEVT_TMU3: bit = INTREQ00_TUNI3; break; case SH4_INTEVT_TMU4: bit = INTREQ00_TUNI4; break; } if ((bit == 0) || (iprreg == NULL)) { return; } _reg_write_4(iprreg, bit); } void intpri_intr_disable(int evtcode) { volatile uint32_t *iprreg; uint32_t bit; if (!CPU_IS_SH4) return; switch (cpu_product) { default: return; case CPU_PRODUCT_7751: case CPU_PRODUCT_7751R: break; } iprreg = (volatile uint32_t *)SH4_INTMSK00; bit = 0; switch (evtcode) { case SH4_INTEVT_PCISERR: case SH4_INTEVT_PCIDMA3: case SH4_INTEVT_PCIDMA2: case SH4_INTEVT_PCIDMA1: case SH4_INTEVT_PCIDMA0: case SH4_INTEVT_PCIPWON: case SH4_INTEVT_PCIPWDWN: case SH4_INTEVT_PCIERR: bit = (1 << ((evtcode - SH4_INTEVT_PCISERR) >> 5)); break; case SH4_INTEVT_TMU3: bit = INTREQ00_TUNI3; break; case SH4_INTEVT_TMU4: bit = INTREQ00_TUNI4; break; } if ((bit == 0) || (iprreg == NULL)) { return; } _reg_write_4(iprreg, bit); } #endif /* SH4 */ /* * Software interrupt support */ void softintr_init(void) { struct sh_soft_intr *asi; int i; for (i = 0; i < _IPL_NSOFT; i++) { asi = &sh_soft_intrs[i]; TAILQ_INIT(&asi->softintr_q); mtx_init(&asi->softintr_lock, IPL_HIGH); asi->softintr_ipl = IPL_SOFT + i; } intc_intr_establish(SH_INTEVT_TMU1_TUNI1, IST_LEVEL, IPL_SOFTNET, tmu1_intr, NULL, "tmu1"); } void softintr_dispatch(int ipl) { struct sh_soft_intr *asi; struct sh_soft_intrhand *sih; asi = &sh_soft_intrs[ipl - IPL_SOFT]; for (;;) { mtx_enter(&asi->softintr_lock); sih = TAILQ_FIRST(&asi->softintr_q); if (sih == NULL) { mtx_leave(&asi->softintr_lock); break; } TAILQ_REMOVE(&asi->softintr_q, sih, sih_q); sih->sih_pending = 0; uvmexp.softs++; mtx_leave(&asi->softintr_lock); (*sih->sih_fn)(sih->sih_arg); } } void setsoft(int ipl) { tmu1_oneshot(); } /* Register a software interrupt handler. */ void * softintr_establish(int ipl, void (*func)(void *), void *arg) { struct sh_soft_intr *asi; struct sh_soft_intrhand *sih; if (__predict_false(ipl >= (IPL_SOFT + _IPL_NSOFT) || ipl < IPL_SOFT)) panic("softintr_establish"); sih = malloc(sizeof(*sih), M_DEVBUF, M_NOWAIT); asi = &sh_soft_intrs[ipl - IPL_SOFT]; if (__predict_true(sih != NULL)) { sih->sih_intrhead = asi; sih->sih_fn = func; sih->sih_arg = arg; sih->sih_pending = 0; } return (sih); } void intr_barrier(void *cookie) { } /* Unregister a software interrupt handler. */ void softintr_disestablish(void *arg) { struct sh_soft_intrhand *sih = arg; struct sh_soft_intr *asi = sih->sih_intrhead; mtx_enter(&asi->softintr_lock); if (sih->sih_pending) { TAILQ_REMOVE(&asi->softintr_q, sih, sih_q); sih->sih_pending = 0; } mtx_leave(&asi->softintr_lock); free(sih, M_DEVBUF, sizeof *sih); } /* Schedule a software interrupt. */ void softintr_schedule(void *arg) { struct sh_soft_intrhand *sih = arg; struct sh_soft_intr *si = sih->sih_intrhead; mtx_enter(&si->softintr_lock); if (sih->sih_pending == 0) { TAILQ_INSERT_TAIL(&si->softintr_q, sih, sih_q); sih->sih_pending = 1; setsoft(si->softintr_ipl); } mtx_leave(&si->softintr_lock); } /* * Software interrupt is simulated with TMU one-shot timer. */ void tmu1_oneshot(void) { _reg_bclr_1(SH_(TSTR), TSTR_STR1); _reg_write_4(SH_(TCNT1), 0); _reg_bset_1(SH_(TSTR), TSTR_STR1); } int tmu1_intr(void *arg) { _reg_bclr_1(SH_(TSTR), TSTR_STR1); _reg_bclr_2(SH_(TCR1), TCR_UNF); softintr_dispatch(IPL_SOFTSERIAL); softintr_dispatch(IPL_SOFTNET); softintr_dispatch(IPL_SOFTCLOCK); softintr_dispatch(IPL_SOFT); return (0); }