125cf1a30Sjl /*
225cf1a30Sjl * CDDL HEADER START
325cf1a30Sjl *
425cf1a30Sjl * The contents of this file are subject to the terms of the
525cf1a30Sjl * Common Development and Distribution License (the "License").
625cf1a30Sjl * You may not use this file except in compliance with the License.
725cf1a30Sjl *
825cf1a30Sjl * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
925cf1a30Sjl * or http://www.opensolaris.org/os/licensing.
1025cf1a30Sjl * See the License for the specific language governing permissions
1125cf1a30Sjl * and limitations under the License.
1225cf1a30Sjl *
1325cf1a30Sjl * When distributing Covered Code, include this CDDL HEADER in each
1425cf1a30Sjl * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
1525cf1a30Sjl * If applicable, add the following below this CDDL HEADER, with the
1625cf1a30Sjl * fields enclosed by brackets "[]" replaced with your own identifying
1725cf1a30Sjl * information: Portions Copyright [yyyy] [name of copyright owner]
1825cf1a30Sjl *
1925cf1a30Sjl * CDDL HEADER END
2025cf1a30Sjl */
2125cf1a30Sjl /*
22*116f7629Sjfrank * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
2325cf1a30Sjl * Use is subject to license terms.
2425cf1a30Sjl */
2525cf1a30Sjl
2625cf1a30Sjl #pragma ident "%Z%%M% %I% %E% SMI"
2725cf1a30Sjl
2825cf1a30Sjl /*
2925cf1a30Sjl * CMU-CH nexus interrupt handling:
3025cf1a30Sjl * PCI device interrupt handler wrapper
3125cf1a30Sjl * pil lookup routine
3225cf1a30Sjl * PCI device interrupt related initchild code
3325cf1a30Sjl */
3425cf1a30Sjl
3525cf1a30Sjl #include <sys/types.h>
3625cf1a30Sjl #include <sys/kmem.h>
3725cf1a30Sjl #include <sys/async.h>
3825cf1a30Sjl #include <sys/spl.h>
3925cf1a30Sjl #include <sys/sunddi.h>
4025cf1a30Sjl #include <sys/machsystm.h>
4125cf1a30Sjl #include <sys/ddi_impldefs.h>
4225cf1a30Sjl #include <sys/pcicmu/pcicmu.h>
4325cf1a30Sjl #include <sys/sdt.h>
4425cf1a30Sjl
4525cf1a30Sjl uint_t pcmu_intr_wrapper(caddr_t arg);
4625cf1a30Sjl
4725cf1a30Sjl /*
4825cf1a30Sjl * interrupt jabber:
4925cf1a30Sjl *
5025cf1a30Sjl * When an interrupt line is jabbering, every time the state machine for the
5125cf1a30Sjl * associated ino is idled, a new mondo will be sent and the ino will go into
5225cf1a30Sjl * the pending state again. The mondo will cause a new call to
5325cf1a30Sjl * pcmu_intr_wrapper() which normally idles the ino's state machine which would
5425cf1a30Sjl * precipitate another trip round the loop.
5525cf1a30Sjl * The loop can be broken by preventing the ino's state machine from being
5625cf1a30Sjl * idled when an interrupt line is jabbering. See the comment at the
5725cf1a30Sjl * beginning of pcmu_intr_wrapper() explaining how the 'interrupt jabber
5825cf1a30Sjl * protection' code does this.
5925cf1a30Sjl */
6025cf1a30Sjl
6125cf1a30Sjl
6225cf1a30Sjl /*
6325cf1a30Sjl * If the unclaimed interrupt count has reached the limit set by
6425cf1a30Sjl * pcmu_unclaimed_intr_max within the time limit, then all interrupts
6525cf1a30Sjl * on this ino is blocked by not idling the interrupt state machine.
6625cf1a30Sjl */
6725cf1a30Sjl static int
pcmu_spurintr(pcmu_ib_ino_info_t * ino_p)6825cf1a30Sjl pcmu_spurintr(pcmu_ib_ino_info_t *ino_p) {
6925cf1a30Sjl int i;
7025cf1a30Sjl ih_t *ih_p = ino_p->pino_ih_start;
7125cf1a30Sjl pcmu_t *pcmu_p = ino_p->pino_ib_p->pib_pcmu_p;
7225cf1a30Sjl char *err_fmt_str;
7325cf1a30Sjl
7425cf1a30Sjl if (ino_p->pino_unclaimed > pcmu_unclaimed_intr_max) {
7525cf1a30Sjl return (DDI_INTR_CLAIMED);
7625cf1a30Sjl }
7725cf1a30Sjl if (!ino_p->pino_unclaimed) {
7825cf1a30Sjl ino_p->pino_spurintr_begin = ddi_get_lbolt();
7925cf1a30Sjl }
8025cf1a30Sjl ino_p->pino_unclaimed++;
8125cf1a30Sjl if (ino_p->pino_unclaimed <= pcmu_unclaimed_intr_max) {
8225cf1a30Sjl goto clear;
8325cf1a30Sjl }
8425cf1a30Sjl if (drv_hztousec(ddi_get_lbolt() - ino_p->pino_spurintr_begin)
8525cf1a30Sjl > pcmu_spurintr_duration) {
8625cf1a30Sjl ino_p->pino_unclaimed = 0;
8725cf1a30Sjl goto clear;
8825cf1a30Sjl }
8925cf1a30Sjl err_fmt_str = "%s%d: ino 0x%x blocked";
9025cf1a30Sjl goto warn;
9125cf1a30Sjl clear:
9225cf1a30Sjl /* clear the pending state */
9325cf1a30Sjl PCMU_IB_INO_INTR_CLEAR(ino_p->pino_clr_reg);
9425cf1a30Sjl err_fmt_str = "!%s%d: spurious interrupt from ino 0x%x";
9525cf1a30Sjl warn:
9625cf1a30Sjl cmn_err(CE_WARN, err_fmt_str, NAMEINST(pcmu_p->pcmu_dip),
9725cf1a30Sjl ino_p->pino_ino);
9825cf1a30Sjl for (i = 0; i < ino_p->pino_ih_size; i++, ih_p = ih_p->ih_next) {
9925cf1a30Sjl cmn_err(CE_CONT, "!%s-%d#%x ", NAMEINST(ih_p->ih_dip),
10025cf1a30Sjl ih_p->ih_inum);
10125cf1a30Sjl }
10225cf1a30Sjl cmn_err(CE_CONT, "!\n");
10325cf1a30Sjl return (DDI_INTR_CLAIMED);
10425cf1a30Sjl }
10525cf1a30Sjl
10625cf1a30Sjl /*
10725cf1a30Sjl * pcmu_intr_wrapper
10825cf1a30Sjl *
10925cf1a30Sjl * This routine is used as wrapper around interrupt handlers installed by child
11025cf1a30Sjl * device drivers. This routine invokes the driver interrupt handlers and
11125cf1a30Sjl * examines the return codes.
11225cf1a30Sjl * There is a count of unclaimed interrupts kept on a per-ino basis. If at
11325cf1a30Sjl * least one handler claims the interrupt then the counter is halved and the
11425cf1a30Sjl * interrupt state machine is idled. If no handler claims the interrupt then
11525cf1a30Sjl * the counter is incremented by one and the state machine is idled.
11625cf1a30Sjl * If the count ever reaches the limit value set by pcmu_unclaimed_intr_max
11725cf1a30Sjl * then the interrupt state machine is not idled thus preventing any further
11825cf1a30Sjl * interrupts on that ino. The state machine will only be idled again if a
11925cf1a30Sjl * handler is subsequently added or removed.
12025cf1a30Sjl *
12125cf1a30Sjl * return value: DDI_INTR_CLAIMED if any handlers claimed the interrupt,
12225cf1a30Sjl * DDI_INTR_UNCLAIMED otherwise.
12325cf1a30Sjl */
12425cf1a30Sjl uint_t
pcmu_intr_wrapper(caddr_t arg)12525cf1a30Sjl pcmu_intr_wrapper(caddr_t arg)
12625cf1a30Sjl {
12725cf1a30Sjl pcmu_ib_ino_info_t *ino_p = (pcmu_ib_ino_info_t *)arg;
12825cf1a30Sjl uint_t result = 0, r;
12925cf1a30Sjl ih_t *ih_p = ino_p->pino_ih_start;
13025cf1a30Sjl int i;
13125cf1a30Sjl #ifdef DEBUG
13225cf1a30Sjl pcmu_t *pcmu_p = ino_p->pino_ib_p->pib_pcmu_p;
13325cf1a30Sjl #endif
13425cf1a30Sjl
13525cf1a30Sjl
13625cf1a30Sjl for (i = 0; i < ino_p->pino_ih_size; i++, ih_p = ih_p->ih_next) {
13725cf1a30Sjl dev_info_t *dip = ih_p->ih_dip;
13825cf1a30Sjl uint_t (*handler)() = ih_p->ih_handler;
13925cf1a30Sjl caddr_t arg1 = ih_p->ih_handler_arg1;
14025cf1a30Sjl caddr_t arg2 = ih_p->ih_handler_arg2;
14125cf1a30Sjl
14225cf1a30Sjl if (ih_p->ih_intr_state == PCMU_INTR_STATE_DISABLE) {
14325cf1a30Sjl PCMU_DBG3(PCMU_DBG_INTR, pcmu_p->pcmu_dip,
14425cf1a30Sjl "pcmu_intr_wrapper: %s%d interrupt %d is "
14525cf1a30Sjl "disabled\n", ddi_driver_name(dip),
14625cf1a30Sjl ddi_get_instance(dip), ino_p->pino_ino);
14725cf1a30Sjl continue;
14825cf1a30Sjl }
14925cf1a30Sjl
15025cf1a30Sjl DTRACE_PROBE4(pcmu__interrupt__start, dev_info_t, dip,
15125cf1a30Sjl void *, handler, caddr_t, arg1, caddr_t, arg2);
15225cf1a30Sjl
15325cf1a30Sjl r = (*handler)(arg1, arg2);
15425cf1a30Sjl DTRACE_PROBE4(pcmu__interrupt__complete, dev_info_t, dip,
15525cf1a30Sjl void *, handler, caddr_t, arg1, int, r);
15625cf1a30Sjl
15725cf1a30Sjl result += r;
15825cf1a30Sjl }
15925cf1a30Sjl
16025cf1a30Sjl if (!result) {
16125cf1a30Sjl return (pcmu_spurintr(ino_p));
16225cf1a30Sjl }
16325cf1a30Sjl ino_p->pino_unclaimed = 0;
16425cf1a30Sjl /* clear the pending state */
16525cf1a30Sjl PCMU_IB_INO_INTR_CLEAR(ino_p->pino_clr_reg);
16625cf1a30Sjl return (DDI_INTR_CLAIMED);
16725cf1a30Sjl }
16825cf1a30Sjl
16925cf1a30Sjl int
pcmu_add_intr(dev_info_t * dip,dev_info_t * rdip,ddi_intr_handle_impl_t * hdlp)17025cf1a30Sjl pcmu_add_intr(dev_info_t *dip, dev_info_t *rdip, ddi_intr_handle_impl_t *hdlp)
17125cf1a30Sjl {
17225cf1a30Sjl pcmu_t *pcmu_p = get_pcmu_soft_state(ddi_get_instance(dip));
17325cf1a30Sjl pcmu_ib_t *pib_p = pcmu_p->pcmu_ib_p;
17425cf1a30Sjl ih_t *ih_p;
17525cf1a30Sjl pcmu_ib_ino_t ino;
17625cf1a30Sjl pcmu_ib_ino_info_t *ino_p; /* pulse interrupts have no ino */
17725cf1a30Sjl pcmu_ib_mondo_t mondo;
17825cf1a30Sjl uint32_t cpu_id;
17925cf1a30Sjl int ret;
18025cf1a30Sjl
18125cf1a30Sjl ino = PCMU_IB_MONDO_TO_INO(hdlp->ih_vector);
18225cf1a30Sjl
18325cf1a30Sjl PCMU_DBG3(PCMU_DBG_A_INTX, dip, "pcmu_add_intr: rdip=%s%d ino=%x\n",
18425cf1a30Sjl ddi_driver_name(rdip), ddi_get_instance(rdip), ino);
18525cf1a30Sjl
18625cf1a30Sjl if (ino > pib_p->pib_max_ino) {
18725cf1a30Sjl PCMU_DBG1(PCMU_DBG_A_INTX, dip, "ino %x is invalid\n", ino);
18825cf1a30Sjl return (DDI_INTR_NOTFOUND);
18925cf1a30Sjl }
19025cf1a30Sjl
19125cf1a30Sjl if ((mondo = PCMU_IB_INO_TO_MONDO(pcmu_p->pcmu_ib_p, ino)) == 0)
19225cf1a30Sjl goto fail1;
19325cf1a30Sjl
19425cf1a30Sjl ino = PCMU_IB_MONDO_TO_INO(mondo);
19525cf1a30Sjl
19625cf1a30Sjl mutex_enter(&pib_p->pib_ino_lst_mutex);
19725cf1a30Sjl ih_p = pcmu_ib_alloc_ih(rdip, hdlp->ih_inum,
19825cf1a30Sjl hdlp->ih_cb_func, hdlp->ih_cb_arg1, hdlp->ih_cb_arg2);
19925cf1a30Sjl
20025cf1a30Sjl if (ino_p = pcmu_ib_locate_ino(pib_p, ino)) { /* sharing ino */
20125cf1a30Sjl uint32_t intr_index = hdlp->ih_inum;
20225cf1a30Sjl if (pcmu_ib_ino_locate_intr(ino_p, rdip, intr_index)) {
20325cf1a30Sjl PCMU_DBG1(PCMU_DBG_A_INTX, dip,
20425cf1a30Sjl "dup intr #%d\n", intr_index);
20525cf1a30Sjl goto fail3;
20625cf1a30Sjl }
20725cf1a30Sjl
20825cf1a30Sjl /*
20925cf1a30Sjl * add default weight(0) to the cpu that we are
21025cf1a30Sjl * already targeting
21125cf1a30Sjl */
21225cf1a30Sjl cpu_id = ino_p->pino_cpuid;
21325cf1a30Sjl intr_dist_cpuid_add_device_weight(cpu_id, rdip, 0);
21425cf1a30Sjl pcmu_ib_ino_add_intr(pcmu_p, ino_p, ih_p);
21525cf1a30Sjl goto ino_done;
21625cf1a30Sjl }
21725cf1a30Sjl
21825cf1a30Sjl ino_p = pcmu_ib_new_ino(pib_p, ino, ih_p);
21925cf1a30Sjl hdlp->ih_vector = mondo;
22025cf1a30Sjl
22125cf1a30Sjl PCMU_DBG2(PCMU_DBG_A_INTX, dip, "pcmu_add_intr: pil=0x%x mondo=0x%x\n",
22225cf1a30Sjl hdlp->ih_pri, hdlp->ih_vector);
22325cf1a30Sjl
22425cf1a30Sjl DDI_INTR_ASSIGN_HDLR_N_ARGS(hdlp,
22525cf1a30Sjl (ddi_intr_handler_t *)pcmu_intr_wrapper, (caddr_t)ino_p, NULL);
22625cf1a30Sjl
22725cf1a30Sjl ret = i_ddi_add_ivintr(hdlp);
22825cf1a30Sjl
22925cf1a30Sjl /*
23025cf1a30Sjl * Restore original interrupt handler
23125cf1a30Sjl * and arguments in interrupt handle.
23225cf1a30Sjl */
23325cf1a30Sjl DDI_INTR_ASSIGN_HDLR_N_ARGS(hdlp, ih_p->ih_handler,
23425cf1a30Sjl ih_p->ih_handler_arg1, ih_p->ih_handler_arg2);
23525cf1a30Sjl
23625cf1a30Sjl if (ret != DDI_SUCCESS) {
23725cf1a30Sjl goto fail4;
23825cf1a30Sjl }
23925cf1a30Sjl /* Save the pil for this ino */
24025cf1a30Sjl ino_p->pino_pil = hdlp->ih_pri;
24125cf1a30Sjl
24225cf1a30Sjl /* clear and enable interrupt */
24325cf1a30Sjl PCMU_IB_INO_INTR_CLEAR(ino_p->pino_clr_reg);
24425cf1a30Sjl
24525cf1a30Sjl /* select cpu for sharing and removal */
24625cf1a30Sjl cpu_id = pcmu_intr_dist_cpuid(pib_p, ino_p);
24725cf1a30Sjl ino_p->pino_cpuid = cpu_id;
24825cf1a30Sjl ino_p->pino_established = 1;
24925cf1a30Sjl intr_dist_cpuid_add_device_weight(cpu_id, rdip, 0);
25025cf1a30Sjl
25125cf1a30Sjl cpu_id = u2u_translate_tgtid(pib_p->pib_pcmu_p,
25225cf1a30Sjl cpu_id, ino_p->pino_map_reg);
25325cf1a30Sjl *ino_p->pino_map_reg = ib_get_map_reg(mondo, cpu_id);
25425cf1a30Sjl *ino_p->pino_map_reg;
25525cf1a30Sjl ino_done:
25625cf1a30Sjl mutex_exit(&pib_p->pib_ino_lst_mutex);
25725cf1a30Sjl done:
25825cf1a30Sjl PCMU_DBG2(PCMU_DBG_A_INTX, dip, "done! Interrupt 0x%x pil=%x\n",
259*116f7629Sjfrank hdlp->ih_vector, hdlp->ih_pri);
26025cf1a30Sjl return (DDI_SUCCESS);
26125cf1a30Sjl fail4:
26225cf1a30Sjl pcmu_ib_delete_ino(pib_p, ino_p);
26325cf1a30Sjl fail3:
26425cf1a30Sjl if (ih_p->ih_config_handle)
26525cf1a30Sjl pci_config_teardown(&ih_p->ih_config_handle);
26625cf1a30Sjl mutex_exit(&pib_p->pib_ino_lst_mutex);
26725cf1a30Sjl kmem_free(ih_p, sizeof (ih_t));
26825cf1a30Sjl fail1:
26925cf1a30Sjl PCMU_DBG2(PCMU_DBG_A_INTX, dip, "Failed! Interrupt 0x%x pil=%x\n",
270*116f7629Sjfrank hdlp->ih_vector, hdlp->ih_pri);
27125cf1a30Sjl return (DDI_FAILURE);
27225cf1a30Sjl }
27325cf1a30Sjl
27425cf1a30Sjl int
pcmu_remove_intr(dev_info_t * dip,dev_info_t * rdip,ddi_intr_handle_impl_t * hdlp)27525cf1a30Sjl pcmu_remove_intr(dev_info_t *dip, dev_info_t *rdip,
27625cf1a30Sjl ddi_intr_handle_impl_t *hdlp)
27725cf1a30Sjl {
27825cf1a30Sjl pcmu_t *pcmu_p = get_pcmu_soft_state(ddi_get_instance(dip));
27925cf1a30Sjl pcmu_ib_t *pib_p = pcmu_p->pcmu_ib_p;
28025cf1a30Sjl pcmu_ib_ino_t ino;
28125cf1a30Sjl pcmu_ib_mondo_t mondo;
28225cf1a30Sjl pcmu_ib_ino_info_t *ino_p; /* non-pulse only */
28325cf1a30Sjl ih_t *ih_p; /* non-pulse only */
28425cf1a30Sjl
28525cf1a30Sjl ino = PCMU_IB_MONDO_TO_INO(hdlp->ih_vector);
28625cf1a30Sjl
28725cf1a30Sjl PCMU_DBG3(PCMU_DBG_R_INTX, dip, "pcmu_rem_intr: rdip=%s%d ino=%x\n",
28825cf1a30Sjl ddi_driver_name(rdip), ddi_get_instance(rdip), ino);
28925cf1a30Sjl
29025cf1a30Sjl /* Translate the interrupt property */
29125cf1a30Sjl mondo = PCMU_IB_INO_TO_MONDO(pcmu_p->pcmu_ib_p, ino);
29225cf1a30Sjl if (mondo == 0) {
29325cf1a30Sjl PCMU_DBG1(PCMU_DBG_R_INTX, dip,
29425cf1a30Sjl "can't get mondo for ino %x\n", ino);
29525cf1a30Sjl return (DDI_FAILURE);
29625cf1a30Sjl }
29725cf1a30Sjl ino = PCMU_IB_MONDO_TO_INO(mondo);
29825cf1a30Sjl
29925cf1a30Sjl mutex_enter(&pib_p->pib_ino_lst_mutex);
30025cf1a30Sjl ino_p = pcmu_ib_locate_ino(pib_p, ino);
30125cf1a30Sjl if (!ino_p) {
30225cf1a30Sjl mutex_exit(&pib_p->pib_ino_lst_mutex);
30325cf1a30Sjl return (DDI_SUCCESS);
30425cf1a30Sjl }
30525cf1a30Sjl
30625cf1a30Sjl ih_p = pcmu_ib_ino_locate_intr(ino_p, rdip, hdlp->ih_inum);
307*116f7629Sjfrank if (pcmu_ib_ino_rem_intr(pcmu_p, ino_p, ih_p) != DDI_SUCCESS) {
308*116f7629Sjfrank mutex_exit(&pib_p->pib_ino_lst_mutex);
309*116f7629Sjfrank return (DDI_FAILURE);
310*116f7629Sjfrank }
31125cf1a30Sjl intr_dist_cpuid_rem_device_weight(ino_p->pino_cpuid, rdip);
31225cf1a30Sjl if (ino_p->pino_ih_size == 0) {
31325cf1a30Sjl PCMU_IB_INO_INTR_PEND(ib_clear_intr_reg_addr(pib_p, ino));
31425cf1a30Sjl hdlp->ih_vector = mondo;
31525cf1a30Sjl i_ddi_rem_ivintr(hdlp);
31625cf1a30Sjl pcmu_ib_delete_ino(pib_p, ino_p);
31725cf1a30Sjl }
31825cf1a30Sjl
31925cf1a30Sjl /* re-enable interrupt only if mapping register still shared */
32025cf1a30Sjl if (ino_p->pino_ih_size) {
32125cf1a30Sjl PCMU_IB_INO_INTR_ON(ino_p->pino_map_reg);
32225cf1a30Sjl *ino_p->pino_map_reg;
32325cf1a30Sjl }
32425cf1a30Sjl mutex_exit(&pib_p->pib_ino_lst_mutex);
32525cf1a30Sjl if (ino_p->pino_ih_size == 0) {
32625cf1a30Sjl kmem_free(ino_p, sizeof (pcmu_ib_ino_info_t));
32725cf1a30Sjl }
32825cf1a30Sjl PCMU_DBG1(PCMU_DBG_R_INTX, dip, "success! mondo=%x\n", mondo);
32925cf1a30Sjl return (DDI_SUCCESS);
33025cf1a30Sjl }
33125cf1a30Sjl
33225cf1a30Sjl /*
33325cf1a30Sjl * free the pcmu_inos array allocated during pcmu_intr_setup. the actual
33425cf1a30Sjl * interrupts are torn down by their respective block destroy routines:
33525cf1a30Sjl * cb_destroy, pcmu_pbm_destroy, and ib_destroy.
33625cf1a30Sjl */
33725cf1a30Sjl void
pcmu_intr_teardown(pcmu_t * pcmu_p)33825cf1a30Sjl pcmu_intr_teardown(pcmu_t *pcmu_p)
33925cf1a30Sjl {
34025cf1a30Sjl kmem_free(pcmu_p->pcmu_inos, pcmu_p->pcmu_inos_len);
34125cf1a30Sjl pcmu_p->pcmu_inos = NULL;
34225cf1a30Sjl pcmu_p->pcmu_inos_len = 0;
34325cf1a30Sjl }
344