1 /* 2 * This file and its contents are supplied under the terms of the 3 * Common Development and Distribution License ("CDDL"), version 1.0. 4 * You may only use this file in accordance with the terms of version 5 * 1.0 of the CDDL. 6 * 7 * A full copy of the text of the CDDL should have accompanied this 8 * source. A copy of the CDDL is also available via the Internet at 9 * http://www.illumos.org/license/CDDL. 10 */ 11 12 /* 13 * Copyright 2016 Joyent, Inc. 14 */ 15 16 /* 17 * ------------------------- 18 * xHCI Interrupt Management 19 * ------------------------- 20 * 21 * Interrupts in the xHCI driver are quite straightforward. We only have a 22 * single interrupt, which is always vector zero. Everything is configured to 23 * use this interrupt. 24 * 25 * ------------------ 26 * Interrupt Claiming 27 * ------------------ 28 * 29 * One of the challenges is knowing when to claim interrupts. Generally 30 * speaking, interrupts for MSI and MSI-X are directed to a specific vector for 31 * a specific device. This allows us to have a bit more confidence on whether 32 * the interrupt is for us. This is contrasted with traditional INTx (pin based) 33 * interrupts in PCI where interrupts are shared between multiple devices. 34 * 35 * xHCI 1.1 / 5.5.2.1 documents the interrupt management register. One of the 36 * quirks here is that when we acknowledge the PCI level MSI or MSI-X, the IP 37 * bit is automatically cleared (see xHCI 1.1 / 4.17.5 for more info). However, 38 * it's not for INTx based systems, thus making things a bit more confusing. 39 * Because of this, we only check the IP bit when we're using INTx interrupts. 40 * 41 * This means that knowing whether or not we can claim something is challenging. 42 * Particularly in the case where we have FM errors. In those cases we opt to 43 * claim rather than not. 44 */ 45 46 #include <sys/usb/hcd/xhci/xhci.h> 47 48 boolean_t 49 xhci_ddi_intr_disable(xhci_t *xhcip) 50 { 51 int ret; 52 53 if (xhcip->xhci_intr_caps & DDI_INTR_FLAG_BLOCK) { 54 if ((ret = ddi_intr_block_disable(&xhcip->xhci_intr_hdl, 55 xhcip->xhci_intr_num)) != DDI_SUCCESS) { 56 xhci_error(xhcip, "failed to block-disable interrupts: " 57 "%d", ret); 58 return (B_FALSE); 59 } 60 } else { 61 if ((ret = ddi_intr_disable(xhcip->xhci_intr_hdl)) != 62 DDI_SUCCESS) { 63 xhci_error(xhcip, "failed to disable interrupt: %d", 64 ret); 65 return (B_FALSE); 66 } 67 } 68 69 return (B_TRUE); 70 } 71 72 73 boolean_t 74 xhci_ddi_intr_enable(xhci_t *xhcip) 75 { 76 int ret; 77 78 if (xhcip->xhci_intr_caps & DDI_INTR_FLAG_BLOCK) { 79 if ((ret = ddi_intr_block_enable(&xhcip->xhci_intr_hdl, 80 xhcip->xhci_intr_num)) != DDI_SUCCESS) { 81 xhci_error(xhcip, "failed to block-enable interrupts: " 82 "%d", ret); 83 return (B_FALSE); 84 } 85 } else { 86 if ((ret = ddi_intr_enable(xhcip->xhci_intr_hdl)) != 87 DDI_SUCCESS) { 88 xhci_error(xhcip, "failed to enable interrupt: %d", 89 ret); 90 return (B_FALSE); 91 } 92 } 93 94 return (B_TRUE); 95 } 96 97 /* 98 * Configure the device for interrupts. We need to take care of three things. 99 * Enabling interupt zero, setting interrupt zero's interrupt moderation, and 100 * then enabling interrupts themselves globally. 101 */ 102 int 103 xhci_intr_conf(xhci_t *xhcip) 104 { 105 uint32_t reg; 106 107 reg = xhci_get32(xhcip, XHCI_R_RUN, XHCI_IMAN(0)); 108 reg |= XHCI_IMAN_INTR_ENA; 109 xhci_put32(xhcip, XHCI_R_RUN, XHCI_IMAN(0), reg); 110 111 xhci_put32(xhcip, XHCI_R_RUN, XHCI_IMOD(0), XHCI_IMOD_DEFAULT); 112 113 reg = xhci_get32(xhcip, XHCI_R_OPER, XHCI_USBCMD); 114 reg |= XHCI_CMD_INTE; 115 xhci_put32(xhcip, XHCI_R_OPER, XHCI_USBCMD, reg); 116 117 if (xhci_check_regs_acc(xhcip) != DDI_FM_OK) { 118 ddi_fm_service_impact(xhcip->xhci_dip, DDI_SERVICE_LOST); 119 return (EIO); 120 } 121 122 return (0); 123 } 124 125 uint_t 126 xhci_intr(caddr_t arg1, caddr_t arg2) 127 { 128 uint32_t iman, status; 129 130 xhci_t *xhcip = (xhci_t *)(void *)arg1; 131 uintptr_t vector = (uintptr_t)arg2; 132 133 ASSERT0(vector); 134 135 /* 136 * First read the status register. 137 */ 138 status = xhci_get32(xhcip, XHCI_R_OPER, XHCI_USBSTS); 139 if (xhci_check_regs_acc(xhcip) != DDI_FM_OK) { 140 xhci_error(xhcip, "failed to read USB status register: " 141 "encountered fatal FM error, resetting device"); 142 xhci_fm_runtime_reset(xhcip); 143 return (DDI_INTR_CLAIMED); 144 } 145 146 /* 147 * Before we read the interrupt management register, check to see if we 148 * have a fatal bit set. At which point, it's time to reset the world 149 * anyway. 150 */ 151 if ((status & (XHCI_STS_HSE | XHCI_STS_SRE | XHCI_STS_HCE)) != 0) { 152 xhci_error(xhcip, "found fatal error bit in status register, " 153 "value: 0x%x: resetting device", status); 154 xhci_fm_runtime_reset(xhcip); 155 return (DDI_INTR_CLAIMED); 156 } 157 158 iman = xhci_get32(xhcip, XHCI_R_RUN, XHCI_IMAN(0)); 159 if (xhci_check_regs_acc(xhcip) != DDI_FM_OK) { 160 xhci_error(xhcip, "failed to read interrupt register 0: " 161 "encountered fatal FM error, resetting device"); 162 xhci_fm_runtime_reset(xhcip); 163 return (DDI_INTR_CLAIMED); 164 } 165 166 /* 167 * When using shared interrupts, verify that this interrupt is for us. 168 * Note that when using MSI and MSI-X, writing to various PCI registers 169 * can automatically clear this for us. 170 */ 171 if (xhcip->xhci_intr_type == DDI_INTR_TYPE_FIXED && 172 (iman & XHCI_IMAN_INTR_PEND) == 0) { 173 return (DDI_INTR_UNCLAIMED); 174 } 175 176 /* 177 * If we detect some kind of error condition here that's going to result 178 * in a device reset being dispatched, we purposefully do not clear the 179 * interrupt and enable it again. 180 */ 181 if (xhci_event_process(xhcip) == B_FALSE) { 182 return (DDI_INTR_CLAIMED); 183 } 184 185 xhci_put32(xhcip, XHCI_R_RUN, XHCI_IMAN(0), iman); 186 if (xhci_check_regs_acc(xhcip) != DDI_FM_OK) { 187 xhci_error(xhcip, "failed to write USB status register: " 188 "encountered fatal FM error, resetting device"); 189 xhci_fm_runtime_reset(xhcip); 190 } 191 192 return (DDI_INTR_CLAIMED); 193 } 194