xref: /illumos-gate/usr/src/uts/common/io/scsi/adapters/smrt/smrt_interrupts.c (revision b8aa3def2e2531e693fba6d1f00a74339a4a663d)
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 (c) 2017, Joyent, Inc.
14  */
15 
16 #include <sys/scsi/adapters/smrt/smrt.h>
17 
18 static char *
smrt_interrupt_type_name(int type)19 smrt_interrupt_type_name(int type)
20 {
21 	switch (type) {
22 	case DDI_INTR_TYPE_MSIX:
23 		return ("MSI-X");
24 	case DDI_INTR_TYPE_MSI:
25 		return ("MSI");
26 	case DDI_INTR_TYPE_FIXED:
27 		return ("fixed");
28 	default:
29 		return ("?");
30 	}
31 }
32 
33 static boolean_t
smrt_try_msix(smrt_t * smrt)34 smrt_try_msix(smrt_t *smrt)
35 {
36 	char *fwver = smrt->smrt_versions.smrtv_firmware_rev;
37 
38 	/*
39 	 * Generation 9 controllers end up having a different firmware
40 	 * versioning scheme than others.  If this is a generation 9 controller,
41 	 * which all share the same PCI device ID, then we default to MSI.
42 	 */
43 	if (smrt->smrt_pci_vendor == SMRT_VENDOR_HP &&
44 	    smrt->smrt_pci_device == SMRT_DEVICE_GEN9) {
45 		return (B_FALSE);
46 	}
47 
48 	if (fwver[0] == '8' && fwver[1] == '.' && isdigit(fwver[2]) &&
49 	    isdigit(fwver[3])) {
50 		/*
51 		 * Version 8.00 of the Smart Array firmware appears to have
52 		 * broken MSI support on at least one controller.  We could
53 		 * blindly try MSI-X everywhere, except that on at least some
54 		 * 6.XX firmware versions, MSI-X interrupts do not appear
55 		 * to be triggered for Simple Transport Method command
56 		 * completions.
57 		 *
58 		 * For now, assume we should try for MSI-X with all 8.XX
59 		 * versions of the firmware.
60 		 */
61 		dev_err(smrt->smrt_dip, CE_NOTE, "!trying MSI-X interrupts "
62 		    "to work around 8.XX firmware defect");
63 		return (B_TRUE);
64 	}
65 
66 	return (B_FALSE);
67 }
68 
69 static int
smrt_interrupts_disable(smrt_t * smrt)70 smrt_interrupts_disable(smrt_t *smrt)
71 {
72 	if (smrt->smrt_interrupt_cap & DDI_INTR_FLAG_BLOCK) {
73 		return (ddi_intr_block_disable(smrt->smrt_interrupts,
74 		    smrt->smrt_ninterrupts));
75 	} else {
76 		VERIFY3S(smrt->smrt_ninterrupts, ==, 1);
77 
78 		return (ddi_intr_disable(smrt->smrt_interrupts[0]));
79 	}
80 }
81 
82 int
smrt_interrupts_enable(smrt_t * smrt)83 smrt_interrupts_enable(smrt_t *smrt)
84 {
85 	int ret;
86 
87 	VERIFY(!(smrt->smrt_init_level & SMRT_INITLEVEL_INT_ENABLED));
88 
89 	if (smrt->smrt_interrupt_cap & DDI_INTR_FLAG_BLOCK) {
90 		ret = ddi_intr_block_enable(smrt->smrt_interrupts,
91 		    smrt->smrt_ninterrupts);
92 	} else {
93 		VERIFY3S(smrt->smrt_ninterrupts, ==, 1);
94 
95 		ret = ddi_intr_enable(smrt->smrt_interrupts[0]);
96 	}
97 
98 	if (ret == DDI_SUCCESS) {
99 		smrt->smrt_init_level |= SMRT_INITLEVEL_INT_ENABLED;
100 	}
101 
102 	return (ret);
103 }
104 
105 static void
smrt_interrupts_free(smrt_t * smrt)106 smrt_interrupts_free(smrt_t *smrt)
107 {
108 	for (int i = 0; i < smrt->smrt_ninterrupts; i++) {
109 		(void) ddi_intr_free(smrt->smrt_interrupts[i]);
110 	}
111 	smrt->smrt_ninterrupts = 0;
112 	smrt->smrt_interrupt_type = 0;
113 	smrt->smrt_interrupt_cap = 0;
114 	smrt->smrt_interrupt_pri = 0;
115 }
116 
117 static int
smrt_interrupts_alloc(smrt_t * smrt,int type)118 smrt_interrupts_alloc(smrt_t *smrt, int type)
119 {
120 	dev_info_t *dip = smrt->smrt_dip;
121 	int nintrs = 0;
122 	int navail = 0;
123 
124 	if (ddi_intr_get_nintrs(dip, type, &nintrs) != DDI_SUCCESS) {
125 		dev_err(dip, CE_WARN, "could not count %s interrupts",
126 		    smrt_interrupt_type_name(type));
127 		return (DDI_FAILURE);
128 	}
129 	if (nintrs < 1) {
130 		dev_err(dip, CE_WARN, "no %s interrupts supported",
131 		    smrt_interrupt_type_name(type));
132 		return (DDI_FAILURE);
133 	}
134 
135 	if (ddi_intr_get_navail(dip, type, &navail) != DDI_SUCCESS) {
136 		dev_err(dip, CE_WARN, "could not count available %s "
137 		    "interrupts", smrt_interrupt_type_name(type));
138 		return (DDI_FAILURE);
139 	}
140 	if (navail < 1) {
141 		dev_err(dip, CE_WARN, "no %s interrupts available",
142 		    smrt_interrupt_type_name(type));
143 		return (DDI_FAILURE);
144 	}
145 
146 	if (ddi_intr_alloc(dip, smrt->smrt_interrupts, type, 0, 1,
147 	    &smrt->smrt_ninterrupts, DDI_INTR_ALLOC_STRICT) != DDI_SUCCESS) {
148 		dev_err(dip, CE_WARN, "%s interrupt allocation failed",
149 		    smrt_interrupt_type_name(type));
150 		smrt_interrupts_free(smrt);
151 		return (DDI_FAILURE);
152 	}
153 
154 	smrt->smrt_init_level |= SMRT_INITLEVEL_INT_ALLOC;
155 	smrt->smrt_interrupt_type = type;
156 	return (DDI_SUCCESS);
157 }
158 
159 int
smrt_interrupts_setup(smrt_t * smrt)160 smrt_interrupts_setup(smrt_t *smrt)
161 {
162 	int types;
163 	unsigned ipri;
164 	uint_t (*hw_isr)(caddr_t, caddr_t);
165 	dev_info_t *dip = smrt->smrt_dip;
166 
167 	/*
168 	 * Select the correct hardware interrupt service routine for the
169 	 * Transport Method we have configured:
170 	 */
171 	switch (smrt->smrt_ctlr_mode) {
172 	case SMRT_CTLR_MODE_SIMPLE:
173 		hw_isr = smrt_isr_hw_simple;
174 		break;
175 	default:
176 		panic("unknown controller mode");
177 	}
178 
179 	if (ddi_intr_get_supported_types(dip, &types) != DDI_SUCCESS) {
180 		dev_err(dip, CE_WARN, "could not get support interrupts");
181 		goto fail;
182 	}
183 
184 	/*
185 	 * At least one firmware version has been released for the Smart Array
186 	 * line with entirely defective MSI support.  The specification is
187 	 * somewhat unclear on the precise nature of MSI-X support with Smart
188 	 * Array controllers, particularly with respect to the Simple Transport
189 	 * Method, but for those broken firmware versions we need to try
190 	 * anyway.
191 	 */
192 	if (smrt_try_msix(smrt) && (types & DDI_INTR_TYPE_MSIX)) {
193 		if (smrt_interrupts_alloc(smrt, DDI_INTR_TYPE_MSIX) ==
194 		    DDI_SUCCESS) {
195 			goto add_handler;
196 		}
197 	}
198 
199 	/*
200 	 * If MSI-X is not available, or not expected to work, fall back to
201 	 * MSI.
202 	 */
203 	if (types & DDI_INTR_TYPE_MSI) {
204 		if (smrt_interrupts_alloc(smrt, DDI_INTR_TYPE_MSI) ==
205 		    DDI_SUCCESS) {
206 			goto add_handler;
207 		}
208 	}
209 
210 	/*
211 	 * If neither MSI-X nor MSI is available, fall back to fixed
212 	 * interrupts.  Note that the use of fixed interrupts has been
213 	 * observed, with some combination of controllers and systems, to
214 	 * result in interrupts stopping completely at random times.
215 	 */
216 	if (types & DDI_INTR_TYPE_FIXED) {
217 		if (smrt_interrupts_alloc(smrt, DDI_INTR_TYPE_FIXED) ==
218 		    DDI_SUCCESS) {
219 			goto add_handler;
220 		}
221 	}
222 
223 	/*
224 	 * We were unable to allocate any interrupts.
225 	 */
226 	dev_err(dip, CE_WARN, "interrupt allocation failed");
227 	goto fail;
228 
229 add_handler:
230 	/*
231 	 * Ensure that we have not been given a high-level interrupt, as our
232 	 * interrupt handlers do not support them.
233 	 */
234 	if (ddi_intr_get_pri(smrt->smrt_interrupts[0], &ipri) != DDI_SUCCESS) {
235 		dev_err(dip, CE_WARN, "could not determine interrupt priority");
236 		goto fail;
237 	}
238 	if (ipri >= ddi_intr_get_hilevel_pri()) {
239 		dev_err(dip, CE_WARN, "high level interrupts not supported");
240 		goto fail;
241 	}
242 	smrt->smrt_interrupt_pri = ipri;
243 
244 	if (ddi_intr_get_cap(smrt->smrt_interrupts[0],
245 	    &smrt->smrt_interrupt_cap) != DDI_SUCCESS) {
246 		dev_err(dip, CE_WARN, "could not get %s interrupt cap",
247 		    smrt_interrupt_type_name(smrt->smrt_interrupt_type));
248 		goto fail;
249 	}
250 
251 	if (ddi_intr_add_handler(smrt->smrt_interrupts[0], hw_isr,
252 	    (caddr_t)smrt, NULL) != DDI_SUCCESS) {
253 		dev_err(dip, CE_WARN, "adding %s interrupt failed",
254 		    smrt_interrupt_type_name(smrt->smrt_interrupt_type));
255 		goto fail;
256 	}
257 	smrt->smrt_init_level |= SMRT_INITLEVEL_INT_ADDED;
258 
259 	return (DDI_SUCCESS);
260 
261 fail:
262 	smrt_interrupts_teardown(smrt);
263 	return (DDI_FAILURE);
264 }
265 
266 void
smrt_interrupts_teardown(smrt_t * smrt)267 smrt_interrupts_teardown(smrt_t *smrt)
268 {
269 	if (smrt->smrt_init_level & SMRT_INITLEVEL_INT_ENABLED) {
270 		(void) smrt_interrupts_disable(smrt);
271 
272 		smrt->smrt_init_level &= ~SMRT_INITLEVEL_INT_ENABLED;
273 	}
274 
275 	if (smrt->smrt_init_level & SMRT_INITLEVEL_INT_ADDED) {
276 		(void) ddi_intr_remove_handler(smrt->smrt_interrupts[0]);
277 
278 		smrt->smrt_init_level &= ~SMRT_INITLEVEL_INT_ADDED;
279 	}
280 
281 	if (smrt->smrt_init_level & SMRT_INITLEVEL_INT_ALLOC) {
282 		smrt_interrupts_free(smrt);
283 
284 		smrt->smrt_init_level &= ~SMRT_INITLEVEL_INT_ALLOC;
285 	}
286 }
287