1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2010 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 /* 27 * px_msi.c 28 */ 29 30 #include <sys/types.h> 31 #include <sys/kmem.h> 32 #include <sys/conf.h> 33 #include <sys/ddi.h> 34 #include <sys/sunddi.h> 35 #include <sys/sunndi.h> 36 #include <sys/modctl.h> 37 #include <sys/disp.h> 38 #include <sys/stat.h> 39 #include <sys/ddi_impldefs.h> 40 #include <sys/pci_impl.h> 41 #include "px_obj.h" 42 43 static int px_msi_get_props(px_t *px_p); 44 45 /* 46 * msi_attach() 47 */ 48 int 49 px_msi_attach(px_t *px_p) 50 { 51 dev_info_t *dip = px_p->px_dip; 52 px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state; 53 ddi_irm_pool_t *irm_pool_p = NULL; 54 ddi_irm_params_t irm_params; 55 msinum_t msi_num; 56 int i, ret; 57 58 DBG(DBG_MSIQ, dip, "px_msi_attach\n"); 59 60 mutex_init(&msi_state_p->msi_mutex, NULL, MUTEX_DRIVER, NULL); 61 62 /* 63 * Check for all MSI related properties and 64 * save all information. 65 */ 66 if (px_msi_get_props(px_p) != DDI_SUCCESS) { 67 px_msi_detach(px_p); 68 return (DDI_FAILURE); 69 } 70 71 px_p->px_supp_intr_types |= (DDI_INTR_TYPE_MSI | DDI_INTR_TYPE_MSIX); 72 73 msi_state_p->msi_p = kmem_zalloc(msi_state_p->msi_cnt * 74 sizeof (px_msi_t), KM_SLEEP); 75 76 for (i = 0, msi_num = msi_state_p->msi_1st_msinum; 77 i < msi_state_p->msi_cnt; i++, msi_num++) { 78 msi_state_p->msi_p[i].msi_msinum = msi_num; 79 msi_state_p->msi_p[i].msi_state = MSI_STATE_FREE; 80 } 81 82 /* 83 * Create IRM pool to manage interrupt allocations. 84 */ 85 bzero(&irm_params, sizeof (ddi_irm_params_t)); 86 irm_params.iparams_types = msi_state_p->msi_type; 87 irm_params.iparams_total = msi_state_p->msi_cnt; 88 if (ndi_irm_create(dip, &irm_params, &irm_pool_p) == DDI_SUCCESS) { 89 msi_state_p->msi_pool_p = irm_pool_p; 90 } else { 91 DBG(DBG_MSIQ, dip, "ndi_irm_create() failed\n"); 92 } 93 94 if ((ret = px_lib_msi_init(dip)) != DDI_SUCCESS) 95 px_msi_detach(px_p); 96 97 return (ret); 98 } 99 100 101 /* 102 * msi_detach() 103 */ 104 void 105 px_msi_detach(px_t *px_p) 106 { 107 dev_info_t *dip = px_p->px_dip; 108 px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state; 109 110 DBG(DBG_MSIQ, dip, "px_msi_detach\n"); 111 112 if (msi_state_p->msi_pool_p) 113 (void) ndi_irm_destroy(msi_state_p->msi_pool_p); 114 115 if (msi_state_p->msi_p) { 116 kmem_free(msi_state_p->msi_p, 117 msi_state_p->msi_cnt * sizeof (px_msi_t)); 118 } 119 120 mutex_destroy(&msi_state_p->msi_mutex); 121 bzero(&px_p->px_ib_p->ib_msi_state, sizeof (px_msi_state_t)); 122 } 123 124 125 /* 126 * msi_alloc() 127 */ 128 /* ARGSUSED */ 129 int 130 px_msi_alloc(px_t *px_p, dev_info_t *rdip, int type, int inum, int msi_count, 131 int flag, int *actual_msi_count_p) 132 { 133 px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state; 134 int first, count, i, n; 135 136 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_alloc: rdip %s:%d " 137 "type 0x%x inum 0x%x msi_count 0x%x\n", ddi_driver_name(rdip), 138 ddi_get_instance(rdip), type, inum, msi_count); 139 140 mutex_enter(&msi_state_p->msi_mutex); 141 142 *actual_msi_count_p = 0; 143 144 /* 145 * MSI interrupts are allocated as contiguous ranges at 146 * power of 2 boundaries from the start of the MSI array. 147 */ 148 if (type == DDI_INTR_TYPE_MSI) { 149 150 /* Search for a range of available interrupts */ 151 for (count = msi_count; count; count >>= 1) { 152 for (first = 0; (first + count) < msi_state_p->msi_cnt; 153 first += count) { 154 for (i = first; i < (first + count); i++) { 155 if (msi_state_p->msi_p[i].msi_state 156 != MSI_STATE_FREE) { 157 break; 158 } 159 } 160 if (i == (first + count)) { 161 goto found_msi; 162 } 163 } 164 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_alloc: failed\n"); 165 if (count > 1) { 166 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_alloc: " 167 "Retry MSI allocation with new msi_count " 168 "0x%x\n", count >> 1); 169 } 170 } 171 172 found_msi: 173 /* Set number of available interrupts */ 174 *actual_msi_count_p = count; 175 176 /* Check if successful, and enforce strict behavior */ 177 if ((count == 0) || 178 ((flag == DDI_INTR_ALLOC_STRICT) && (count != msi_count))) { 179 mutex_exit(&msi_state_p->msi_mutex); 180 return (DDI_EAGAIN); 181 } 182 183 /* Allocate the interrupts */ 184 for (i = first; i < (first + count); i++, inum++) { 185 msi_state_p->msi_p[i].msi_state = MSI_STATE_INUSE; 186 msi_state_p->msi_p[i].msi_dip = rdip; 187 msi_state_p->msi_p[i].msi_inum = inum; 188 } 189 } 190 191 /* 192 * MSI-X interrupts are allocated from the end of the MSI 193 * array. There are no concerns about power of 2 boundaries 194 * and the allocated interrupts do not have to be contiguous. 195 */ 196 if (type == DDI_INTR_TYPE_MSIX) { 197 198 /* Count available interrupts, up to count requested */ 199 for (count = 0, i = (msi_state_p->msi_cnt - 1); i >= 0; i--) { 200 if (msi_state_p->msi_p[i].msi_state == MSI_STATE_FREE) { 201 if (count == 0) 202 first = i; 203 count++; 204 if (count == msi_count) 205 break; 206 } 207 } 208 209 /* Set number of available interrupts */ 210 *actual_msi_count_p = count; 211 212 /* Check if successful, and enforce strict behavior */ 213 if ((count == 0) || 214 ((flag == DDI_INTR_ALLOC_STRICT) && (count != msi_count))) { 215 mutex_exit(&msi_state_p->msi_mutex); 216 return (DDI_EAGAIN); 217 } 218 219 /* Allocate the interrupts */ 220 for (n = 0, i = first; n < count; i--) { 221 if (msi_state_p->msi_p[i].msi_state != MSI_STATE_FREE) 222 continue; 223 msi_state_p->msi_p[i].msi_state = MSI_STATE_INUSE; 224 msi_state_p->msi_p[i].msi_dip = rdip; 225 msi_state_p->msi_p[i].msi_inum = inum; 226 inum++; 227 n++; 228 } 229 } 230 231 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_alloc: rdip %s:%d " 232 "msi_num 0x%x count 0x%x\n", ddi_driver_name(rdip), 233 ddi_get_instance(rdip), first, count); 234 235 mutex_exit(&msi_state_p->msi_mutex); 236 237 return (DDI_SUCCESS); 238 } 239 240 241 /* 242 * msi_free() 243 */ 244 int 245 px_msi_free(px_t *px_p, dev_info_t *rdip, int inum, int msi_count) 246 { 247 px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state; 248 int i, n; 249 250 DBG(DBG_R_MSIX, px_p->px_dip, "px_msi_free: rdip 0x%p " 251 "inum 0x%x msi_count 0x%x\n", rdip, inum, msi_count); 252 253 mutex_enter(&msi_state_p->msi_mutex); 254 255 /* 256 * Find and release the specified MSI/X numbers. 257 * 258 * Because the allocations are not always contiguous, perform 259 * a full linear search of the MSI/X table looking for MSI/X 260 * vectors owned by the device with inum values in the range 261 * [inum .. (inum + msi_count - 1)]. 262 */ 263 for (i = 0, n = 0; (i < msi_state_p->msi_cnt) && (n < msi_count); i++) { 264 if ((msi_state_p->msi_p[i].msi_dip == rdip) && 265 (msi_state_p->msi_p[i].msi_inum >= inum) && 266 (msi_state_p->msi_p[i].msi_inum < (inum + msi_count))) { 267 msi_state_p->msi_p[i].msi_dip = NULL; 268 msi_state_p->msi_p[i].msi_inum = 0; 269 msi_state_p->msi_p[i].msi_msiq_id = 0; 270 msi_state_p->msi_p[i].msi_state = MSI_STATE_FREE; 271 n++; 272 } 273 } 274 275 mutex_exit(&msi_state_p->msi_mutex); 276 277 /* Fail if the MSI/X numbers were not found */ 278 if (n < msi_count) 279 return (DDI_FAILURE); 280 281 return (DDI_SUCCESS); 282 } 283 284 /* 285 * msi_get_msinum() 286 */ 287 int 288 px_msi_get_msinum(px_t *px_p, dev_info_t *rdip, int inum, msinum_t *msi_num_p) 289 { 290 px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state; 291 int i; 292 293 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_get_msinum: " 294 "rdip 0x%p inum 0x%x\n", rdip, inum); 295 296 mutex_enter(&msi_state_p->msi_mutex); 297 298 for (i = 0; i < msi_state_p->msi_cnt; i++) { 299 if ((msi_state_p->msi_p[i].msi_inum == inum) && 300 (msi_state_p->msi_p[i].msi_dip == rdip)) { 301 302 *msi_num_p = msi_state_p->msi_p[i].msi_msinum; 303 304 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_get_msinum: " 305 "inum 0x%x msi 0x%x\n", inum, *msi_num_p); 306 307 mutex_exit(&msi_state_p->msi_mutex); 308 return (DDI_SUCCESS); 309 } 310 } 311 312 if (i >= msi_state_p->msi_cnt) 313 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_get_msinum: " 314 "no msi for inum 0x%x\n", inum); 315 316 mutex_exit(&msi_state_p->msi_mutex); 317 return (DDI_FAILURE); 318 } 319 320 /* 321 * px_msi_get_props() 322 */ 323 static int 324 px_msi_get_props(px_t *px_p) 325 { 326 dev_info_t *dip = px_p->px_dip; 327 px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state; 328 int length = sizeof (int); 329 int *valuep = NULL; 330 uint64_t msi_addr_hi, msi_addr_lo; 331 332 DBG(DBG_MSIQ, dip, "px_msi_get_props\n"); 333 334 /* #msi */ 335 msi_state_p->msi_cnt = ddi_getprop(DDI_DEV_T_ANY, dip, 336 DDI_PROP_DONTPASS, "#msi", 0); 337 338 DBG(DBG_MSIQ, dip, "#msi=%d\n", msi_state_p->msi_cnt); 339 if (msi_state_p->msi_cnt == 0) 340 return (DDI_FAILURE); 341 342 /* msi-ranges: msi# field */ 343 if (ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_ALLOC, 344 DDI_PROP_DONTPASS, "msi-ranges", (caddr_t)&valuep, &length) 345 != DDI_PROP_SUCCESS) 346 return (DDI_FAILURE); 347 348 msi_state_p->msi_1st_msinum = ((px_msi_ranges_t *)valuep)->msi_no; 349 kmem_free(valuep, (size_t)length); 350 351 DBG(DBG_MSIQ, dip, "msi_1st_msinum=%d\n", msi_state_p->msi_1st_msinum); 352 353 /* msi-data-mask */ 354 msi_state_p->msi_data_mask = ddi_getprop(DDI_DEV_T_ANY, dip, 355 DDI_PROP_DONTPASS, "msi-data-mask", 0); 356 357 DBG(DBG_MSIQ, dip, "msi-data-mask=0x%x\n", 358 msi_state_p->msi_data_mask); 359 360 /* msi-data-width */ 361 msi_state_p->msi_data_width = ddi_getprop(DDI_DEV_T_ANY, dip, 362 DDI_PROP_DONTPASS, "msix-data-width", 0); 363 364 DBG(DBG_MSIQ, dip, "msix-data-width=%d\n", 365 msi_state_p->msi_data_width); 366 367 /* 368 * Assume MSI is always supported, but also check if MSIX is supported 369 */ 370 if (msi_state_p->msi_data_width) { 371 msi_state_p->msi_type = DDI_INTR_TYPE_MSI; 372 if (msi_state_p->msi_data_width == PX_MSIX_WIDTH) 373 msi_state_p->msi_type |= DDI_INTR_TYPE_MSIX; 374 } else { 375 return (DDI_FAILURE); 376 } 377 378 /* msi-address-ranges */ 379 if (ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_ALLOC, 380 DDI_PROP_DONTPASS, "msi-address-ranges", (caddr_t)&valuep, &length) 381 != DDI_PROP_SUCCESS) 382 return (DDI_FAILURE); 383 384 msi_addr_hi = ((px_msi_address_ranges_t *)valuep)->msi_addr32_hi; 385 msi_addr_lo = ((px_msi_address_ranges_t *)valuep)->msi_addr32_lo; 386 msi_state_p->msi_addr32 = (msi_addr_hi << 32) | msi_addr_lo; 387 msi_state_p->msi_addr32_len = 388 ((px_msi_address_ranges_t *)valuep)->msi_addr32_len; 389 390 msi_addr_hi = ((px_msi_address_ranges_t *)valuep)->msi_addr64_hi; 391 msi_addr_lo = ((px_msi_address_ranges_t *)valuep)->msi_addr64_lo; 392 msi_state_p->msi_addr64 = (msi_addr_hi << 32) | msi_addr_lo; 393 msi_state_p->msi_addr64_len = 394 ((px_msi_address_ranges_t *)valuep)->msi_addr64_len; 395 396 DBG(DBG_MSIQ, dip, "msi_addr32=0x%llx\n", msi_state_p->msi_addr32); 397 DBG(DBG_MSIQ, dip, "msi_addr64=0x%llx\n", msi_state_p->msi_addr64); 398 399 kmem_free(valuep, (size_t)length); 400 return (DDI_SUCCESS); 401 } 402