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
48boolean_t
49xhci_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
73boolean_t
74xhci_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 */
102int
103xhci_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
125uint_t
126xhci_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