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 * 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 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 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 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 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 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 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 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