/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * CMU-CH nexus interrupt handling: * PCI device interrupt handler wrapper * pil lookup routine * PCI device interrupt related initchild code */ #include #include #include #include #include #include #include #include #include uint_t pcmu_intr_wrapper(caddr_t arg); /* * interrupt jabber: * * When an interrupt line is jabbering, every time the state machine for the * associated ino is idled, a new mondo will be sent and the ino will go into * the pending state again. The mondo will cause a new call to * pcmu_intr_wrapper() which normally idles the ino's state machine which would * precipitate another trip round the loop. * The loop can be broken by preventing the ino's state machine from being * idled when an interrupt line is jabbering. See the comment at the * beginning of pcmu_intr_wrapper() explaining how the 'interrupt jabber * protection' code does this. */ /* * If the unclaimed interrupt count has reached the limit set by * pcmu_unclaimed_intr_max within the time limit, then all interrupts * on this ino is blocked by not idling the interrupt state machine. */ static int pcmu_spurintr(pcmu_ib_ino_info_t *ino_p) { int i; ih_t *ih_p = ino_p->pino_ih_start; pcmu_t *pcmu_p = ino_p->pino_ib_p->pib_pcmu_p; char *err_fmt_str; if (ino_p->pino_unclaimed > pcmu_unclaimed_intr_max) { return (DDI_INTR_CLAIMED); } if (!ino_p->pino_unclaimed) { ino_p->pino_spurintr_begin = ddi_get_lbolt(); } ino_p->pino_unclaimed++; if (ino_p->pino_unclaimed <= pcmu_unclaimed_intr_max) { goto clear; } if (drv_hztousec(ddi_get_lbolt() - ino_p->pino_spurintr_begin) > pcmu_spurintr_duration) { ino_p->pino_unclaimed = 0; goto clear; } err_fmt_str = "%s%d: ino 0x%x blocked"; goto warn; clear: /* clear the pending state */ PCMU_IB_INO_INTR_CLEAR(ino_p->pino_clr_reg); err_fmt_str = "!%s%d: spurious interrupt from ino 0x%x"; warn: cmn_err(CE_WARN, err_fmt_str, NAMEINST(pcmu_p->pcmu_dip), ino_p->pino_ino); for (i = 0; i < ino_p->pino_ih_size; i++, ih_p = ih_p->ih_next) { cmn_err(CE_CONT, "!%s-%d#%x ", NAMEINST(ih_p->ih_dip), ih_p->ih_inum); } cmn_err(CE_CONT, "!\n"); return (DDI_INTR_CLAIMED); } /* * pcmu_intr_wrapper * * This routine is used as wrapper around interrupt handlers installed by child * device drivers. This routine invokes the driver interrupt handlers and * examines the return codes. * There is a count of unclaimed interrupts kept on a per-ino basis. If at * least one handler claims the interrupt then the counter is halved and the * interrupt state machine is idled. If no handler claims the interrupt then * the counter is incremented by one and the state machine is idled. * If the count ever reaches the limit value set by pcmu_unclaimed_intr_max * then the interrupt state machine is not idled thus preventing any further * interrupts on that ino. The state machine will only be idled again if a * handler is subsequently added or removed. * * return value: DDI_INTR_CLAIMED if any handlers claimed the interrupt, * DDI_INTR_UNCLAIMED otherwise. */ uint_t pcmu_intr_wrapper(caddr_t arg) { pcmu_ib_ino_info_t *ino_p = (pcmu_ib_ino_info_t *)arg; uint_t result = 0, r; ih_t *ih_p = ino_p->pino_ih_start; int i; #ifdef DEBUG pcmu_t *pcmu_p = ino_p->pino_ib_p->pib_pcmu_p; #endif for (i = 0; i < ino_p->pino_ih_size; i++, ih_p = ih_p->ih_next) { dev_info_t *dip = ih_p->ih_dip; uint_t (*handler)() = ih_p->ih_handler; caddr_t arg1 = ih_p->ih_handler_arg1; caddr_t arg2 = ih_p->ih_handler_arg2; if (ih_p->ih_intr_state == PCMU_INTR_STATE_DISABLE) { PCMU_DBG3(PCMU_DBG_INTR, pcmu_p->pcmu_dip, "pcmu_intr_wrapper: %s%d interrupt %d is " "disabled\n", ddi_driver_name(dip), ddi_get_instance(dip), ino_p->pino_ino); continue; } DTRACE_PROBE4(pcmu__interrupt__start, dev_info_t, dip, void *, handler, caddr_t, arg1, caddr_t, arg2); r = (*handler)(arg1, arg2); DTRACE_PROBE4(pcmu__interrupt__complete, dev_info_t, dip, void *, handler, caddr_t, arg1, int, r); result += r; } if (!result) { return (pcmu_spurintr(ino_p)); } ino_p->pino_unclaimed = 0; /* clear the pending state */ PCMU_IB_INO_INTR_CLEAR(ino_p->pino_clr_reg); return (DDI_INTR_CLAIMED); } int pcmu_add_intr(dev_info_t *dip, dev_info_t *rdip, ddi_intr_handle_impl_t *hdlp) { pcmu_t *pcmu_p = get_pcmu_soft_state(ddi_get_instance(dip)); pcmu_ib_t *pib_p = pcmu_p->pcmu_ib_p; ih_t *ih_p; pcmu_ib_ino_t ino; pcmu_ib_ino_info_t *ino_p; /* pulse interrupts have no ino */ pcmu_ib_mondo_t mondo; uint32_t cpu_id; int ret; ino = PCMU_IB_MONDO_TO_INO(hdlp->ih_vector); PCMU_DBG3(PCMU_DBG_A_INTX, dip, "pcmu_add_intr: rdip=%s%d ino=%x\n", ddi_driver_name(rdip), ddi_get_instance(rdip), ino); if (ino > pib_p->pib_max_ino) { PCMU_DBG1(PCMU_DBG_A_INTX, dip, "ino %x is invalid\n", ino); return (DDI_INTR_NOTFOUND); } if ((mondo = PCMU_IB_INO_TO_MONDO(pcmu_p->pcmu_ib_p, ino)) == 0) goto fail1; ino = PCMU_IB_MONDO_TO_INO(mondo); mutex_enter(&pib_p->pib_ino_lst_mutex); ih_p = pcmu_ib_alloc_ih(rdip, hdlp->ih_inum, hdlp->ih_cb_func, hdlp->ih_cb_arg1, hdlp->ih_cb_arg2); if (ino_p = pcmu_ib_locate_ino(pib_p, ino)) { /* sharing ino */ uint32_t intr_index = hdlp->ih_inum; if (pcmu_ib_ino_locate_intr(ino_p, rdip, intr_index)) { PCMU_DBG1(PCMU_DBG_A_INTX, dip, "dup intr #%d\n", intr_index); goto fail3; } /* * add default weight(0) to the cpu that we are * already targeting */ cpu_id = ino_p->pino_cpuid; intr_dist_cpuid_add_device_weight(cpu_id, rdip, 0); pcmu_ib_ino_add_intr(pcmu_p, ino_p, ih_p); goto ino_done; } ino_p = pcmu_ib_new_ino(pib_p, ino, ih_p); hdlp->ih_vector = mondo; PCMU_DBG2(PCMU_DBG_A_INTX, dip, "pcmu_add_intr: pil=0x%x mondo=0x%x\n", hdlp->ih_pri, hdlp->ih_vector); DDI_INTR_ASSIGN_HDLR_N_ARGS(hdlp, (ddi_intr_handler_t *)pcmu_intr_wrapper, (caddr_t)ino_p, NULL); ret = i_ddi_add_ivintr(hdlp); /* * Restore original interrupt handler * and arguments in interrupt handle. */ DDI_INTR_ASSIGN_HDLR_N_ARGS(hdlp, ih_p->ih_handler, ih_p->ih_handler_arg1, ih_p->ih_handler_arg2); if (ret != DDI_SUCCESS) { goto fail4; } /* Save the pil for this ino */ ino_p->pino_pil = hdlp->ih_pri; /* clear and enable interrupt */ PCMU_IB_INO_INTR_CLEAR(ino_p->pino_clr_reg); /* select cpu for sharing and removal */ cpu_id = pcmu_intr_dist_cpuid(pib_p, ino_p); ino_p->pino_cpuid = cpu_id; ino_p->pino_established = 1; intr_dist_cpuid_add_device_weight(cpu_id, rdip, 0); cpu_id = u2u_translate_tgtid(pib_p->pib_pcmu_p, cpu_id, ino_p->pino_map_reg); *ino_p->pino_map_reg = ib_get_map_reg(mondo, cpu_id); *ino_p->pino_map_reg; ino_done: mutex_exit(&pib_p->pib_ino_lst_mutex); done: PCMU_DBG2(PCMU_DBG_A_INTX, dip, "done! Interrupt 0x%x pil=%x\n", hdlp->ih_vector, hdlp->ih_pri); return (DDI_SUCCESS); fail4: pcmu_ib_delete_ino(pib_p, ino_p); fail3: if (ih_p->ih_config_handle) pci_config_teardown(&ih_p->ih_config_handle); mutex_exit(&pib_p->pib_ino_lst_mutex); kmem_free(ih_p, sizeof (ih_t)); fail1: PCMU_DBG2(PCMU_DBG_A_INTX, dip, "Failed! Interrupt 0x%x pil=%x\n", hdlp->ih_vector, hdlp->ih_pri); return (DDI_FAILURE); } int pcmu_remove_intr(dev_info_t *dip, dev_info_t *rdip, ddi_intr_handle_impl_t *hdlp) { pcmu_t *pcmu_p = get_pcmu_soft_state(ddi_get_instance(dip)); pcmu_ib_t *pib_p = pcmu_p->pcmu_ib_p; pcmu_ib_ino_t ino; pcmu_ib_mondo_t mondo; pcmu_ib_ino_info_t *ino_p; /* non-pulse only */ ih_t *ih_p; /* non-pulse only */ ino = PCMU_IB_MONDO_TO_INO(hdlp->ih_vector); PCMU_DBG3(PCMU_DBG_R_INTX, dip, "pcmu_rem_intr: rdip=%s%d ino=%x\n", ddi_driver_name(rdip), ddi_get_instance(rdip), ino); /* Translate the interrupt property */ mondo = PCMU_IB_INO_TO_MONDO(pcmu_p->pcmu_ib_p, ino); if (mondo == 0) { PCMU_DBG1(PCMU_DBG_R_INTX, dip, "can't get mondo for ino %x\n", ino); return (DDI_FAILURE); } ino = PCMU_IB_MONDO_TO_INO(mondo); mutex_enter(&pib_p->pib_ino_lst_mutex); ino_p = pcmu_ib_locate_ino(pib_p, ino); if (!ino_p) { mutex_exit(&pib_p->pib_ino_lst_mutex); return (DDI_SUCCESS); } ih_p = pcmu_ib_ino_locate_intr(ino_p, rdip, hdlp->ih_inum); if (pcmu_ib_ino_rem_intr(pcmu_p, ino_p, ih_p) != DDI_SUCCESS) { mutex_exit(&pib_p->pib_ino_lst_mutex); return (DDI_FAILURE); } intr_dist_cpuid_rem_device_weight(ino_p->pino_cpuid, rdip); if (ino_p->pino_ih_size == 0) { PCMU_IB_INO_INTR_PEND(ib_clear_intr_reg_addr(pib_p, ino)); hdlp->ih_vector = mondo; i_ddi_rem_ivintr(hdlp); pcmu_ib_delete_ino(pib_p, ino_p); } /* re-enable interrupt only if mapping register still shared */ if (ino_p->pino_ih_size) { PCMU_IB_INO_INTR_ON(ino_p->pino_map_reg); *ino_p->pino_map_reg; } mutex_exit(&pib_p->pib_ino_lst_mutex); if (ino_p->pino_ih_size == 0) { kmem_free(ino_p, sizeof (pcmu_ib_ino_info_t)); } PCMU_DBG1(PCMU_DBG_R_INTX, dip, "success! mondo=%x\n", mondo); return (DDI_SUCCESS); } /* * free the pcmu_inos array allocated during pcmu_intr_setup. the actual * interrupts are torn down by their respective block destroy routines: * cb_destroy, pcmu_pbm_destroy, and ib_destroy. */ void pcmu_intr_teardown(pcmu_t *pcmu_p) { kmem_free(pcmu_p->pcmu_inos, pcmu_p->pcmu_inos_len); pcmu_p->pcmu_inos = NULL; pcmu_p->pcmu_inos_len = 0; }