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