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