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
xhci_ddi_intr_disable(xhci_t * xhcip)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
xhci_ddi_intr_enable(xhci_t * xhcip)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
xhci_intr_conf(xhci_t * xhcip)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
xhci_intr(caddr_t arg1,caddr_t arg2)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