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 2009 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 msi_state_p->msi_p = kmem_zalloc(msi_state_p->msi_cnt * 72 sizeof (px_msi_t), KM_SLEEP); 73 74 for (i = 0, msi_num = msi_state_p->msi_1st_msinum; 75 i < msi_state_p->msi_cnt; i++, msi_num++) { 76 msi_state_p->msi_p[i].msi_msinum = msi_num; 77 msi_state_p->msi_p[i].msi_state = MSI_STATE_FREE; 78 } 79 80 /* 81 * Create IRM pool to manage interrupt allocations. 82 */ 83 bzero(&irm_params, sizeof (ddi_irm_params_t)); 84 irm_params.iparams_types = msi_state_p->msi_type; 85 irm_params.iparams_total = msi_state_p->msi_cnt; 86 if (ndi_irm_create(dip, &irm_params, &irm_pool_p) == DDI_SUCCESS) { 87 msi_state_p->msi_pool_p = irm_pool_p; 88 } else { 89 DBG(DBG_MSIQ, dip, "ndi_irm_create() failed\n"); 90 } 91 92 if ((ret = px_lib_msi_init(dip)) != DDI_SUCCESS) 93 px_msi_detach(px_p); 94 95 return (ret); 96 } 97 98 99 /* 100 * msi_detach() 101 */ 102 void 103 px_msi_detach(px_t *px_p) 104 { 105 dev_info_t *dip = px_p->px_dip; 106 px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state; 107 108 DBG(DBG_MSIQ, dip, "px_msi_detach\n"); 109 110 if (msi_state_p->msi_pool_p) 111 (void) ndi_irm_destroy(msi_state_p->msi_pool_p); 112 113 if (msi_state_p->msi_p) { 114 kmem_free(msi_state_p->msi_p, 115 msi_state_p->msi_cnt * sizeof (px_msi_t)); 116 } 117 118 mutex_destroy(&msi_state_p->msi_mutex); 119 bzero(&px_p->px_ib_p->ib_msi_state, sizeof (px_msi_state_t)); 120 } 121 122 123 /* 124 * msi_alloc() 125 */ 126 /* ARGSUSED */ 127 int 128 px_msi_alloc(px_t *px_p, dev_info_t *rdip, int type, int inum, int msi_count, 129 int flag, int *actual_msi_count_p) 130 { 131 px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state; 132 int first, count, i, n; 133 134 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_alloc: rdip %s:%d " 135 "type 0x%x inum 0x%x msi_count 0x%x\n", ddi_driver_name(rdip), 136 ddi_get_instance(rdip), type, inum, msi_count); 137 138 mutex_enter(&msi_state_p->msi_mutex); 139 140 *actual_msi_count_p = 0; 141 142 /* 143 * MSI interrupts are allocated as contiguous ranges at 144 * power of 2 boundaries from the start of the MSI array. 145 */ 146 if (type == DDI_INTR_TYPE_MSI) { 147 148 /* Search for a range of available interrupts */ 149 for (count = msi_count; count; count >>= 1) { 150 for (first = 0; (first + count) < msi_state_p->msi_cnt; 151 first += count) { 152 for (i = first; i < (first + count); i++) { 153 if (msi_state_p->msi_p[i].msi_state 154 != MSI_STATE_FREE) { 155 break; 156 } 157 } 158 if (i == (first + count)) { 159 goto found_msi; 160 } 161 } 162 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_alloc: failed\n"); 163 if (count > 1) { 164 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_alloc: " 165 "Retry MSI allocation with new msi_count " 166 "0x%x\n", count >> 1); 167 } 168 } 169 170 found_msi: 171 /* Set number of available interrupts */ 172 *actual_msi_count_p = count; 173 174 /* Check if successful, and enforce strict behavior */ 175 if ((count == 0) || 176 ((flag == DDI_INTR_ALLOC_STRICT) && (count != msi_count))) { 177 mutex_exit(&msi_state_p->msi_mutex); 178 return (DDI_EAGAIN); 179 } 180 181 /* Allocate the interrupts */ 182 for (i = first; i < (first + count); i++, inum++) { 183 msi_state_p->msi_p[i].msi_state = MSI_STATE_INUSE; 184 msi_state_p->msi_p[i].msi_dip = rdip; 185 msi_state_p->msi_p[i].msi_inum = inum; 186 } 187 } 188 189 /* 190 * MSI-X interrupts are allocated from the end of the MSI 191 * array. There are no concerns about power of 2 boundaries 192 * and the allocated interrupts do not have to be contiguous. 193 */ 194 if (type == DDI_INTR_TYPE_MSIX) { 195 196 /* Count available interrupts, up to count requested */ 197 for (count = 0, i = (msi_state_p->msi_cnt - 1); i >= 0; i--) { 198 if (msi_state_p->msi_p[i].msi_state == MSI_STATE_FREE) { 199 if (count == 0) 200 first = i; 201 count++; 202 if (count == msi_count) 203 break; 204 } 205 } 206 207 /* Set number of available interrupts */ 208 *actual_msi_count_p = count; 209 210 /* Check if successful, and enforce strict behavior */ 211 if ((count == 0) || 212 ((flag == DDI_INTR_ALLOC_STRICT) && (count != msi_count))) { 213 mutex_exit(&msi_state_p->msi_mutex); 214 return (DDI_EAGAIN); 215 } 216 217 /* Allocate the interrupts */ 218 for (n = 0, i = first; n < count; i--) { 219 if (msi_state_p->msi_p[i].msi_state != MSI_STATE_FREE) 220 continue; 221 msi_state_p->msi_p[i].msi_state = MSI_STATE_INUSE; 222 msi_state_p->msi_p[i].msi_dip = rdip; 223 msi_state_p->msi_p[i].msi_inum = inum; 224 inum++; 225 n++; 226 } 227 } 228 229 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_alloc: rdip %s:%d " 230 "msi_num 0x%x count 0x%x\n", ddi_driver_name(rdip), 231 ddi_get_instance(rdip), first, count); 232 233 mutex_exit(&msi_state_p->msi_mutex); 234 235 return (DDI_SUCCESS); 236 } 237 238 239 /* 240 * msi_free() 241 */ 242 int 243 px_msi_free(px_t *px_p, dev_info_t *rdip, int inum, int msi_count) 244 { 245 px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state; 246 int i, n; 247 248 DBG(DBG_R_MSIX, px_p->px_dip, "px_msi_free: rdip 0x%p " 249 "inum 0x%x msi_count 0x%x\n", rdip, inum, msi_count); 250 251 mutex_enter(&msi_state_p->msi_mutex); 252 253 /* 254 * Find and release the specified MSI/X numbers. 255 * 256 * Because the allocations are not always contiguous, perform 257 * a full linear search of the MSI/X table looking for MSI/X 258 * vectors owned by the device with inum values in the range 259 * [inum .. (inum + msi_count - 1)]. 260 */ 261 for (i = 0, n = 0; (i < msi_state_p->msi_cnt) && (n < msi_count); i++) { 262 if ((msi_state_p->msi_p[i].msi_dip == rdip) && 263 (msi_state_p->msi_p[i].msi_inum >= inum) && 264 (msi_state_p->msi_p[i].msi_inum < (inum + msi_count))) { 265 msi_state_p->msi_p[i].msi_dip = NULL; 266 msi_state_p->msi_p[i].msi_inum = 0; 267 msi_state_p->msi_p[i].msi_msiq_id = 0; 268 msi_state_p->msi_p[i].msi_state = MSI_STATE_FREE; 269 n++; 270 } 271 } 272 273 mutex_exit(&msi_state_p->msi_mutex); 274 275 /* Fail if the MSI/X numbers were not found */ 276 if (n < msi_count) 277 return (DDI_FAILURE); 278 279 return (DDI_SUCCESS); 280 } 281 282 /* 283 * msi_get_msinum() 284 */ 285 int 286 px_msi_get_msinum(px_t *px_p, dev_info_t *rdip, int inum, msinum_t *msi_num_p) 287 { 288 px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state; 289 int i; 290 291 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_get_msinum: " 292 "rdip 0x%p inum 0x%x\n", rdip, inum); 293 294 mutex_enter(&msi_state_p->msi_mutex); 295 296 for (i = 0; i < msi_state_p->msi_cnt; i++) { 297 if ((msi_state_p->msi_p[i].msi_inum == inum) && 298 (msi_state_p->msi_p[i].msi_dip == rdip)) { 299 300 *msi_num_p = msi_state_p->msi_p[i].msi_msinum; 301 302 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_get_msinum: " 303 "inum 0x%x msi 0x%x\n", inum, *msi_num_p); 304 305 mutex_exit(&msi_state_p->msi_mutex); 306 return (DDI_SUCCESS); 307 } 308 } 309 310 if (i >= msi_state_p->msi_cnt) 311 DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_get_msinum: " 312 "no msi for inum 0x%x\n", inum); 313 314 mutex_exit(&msi_state_p->msi_mutex); 315 return (DDI_FAILURE); 316 } 317 318 /* 319 * px_msi_get_props() 320 */ 321 static int 322 px_msi_get_props(px_t *px_p) 323 { 324 dev_info_t *dip = px_p->px_dip; 325 px_msi_state_t *msi_state_p = &px_p->px_ib_p->ib_msi_state; 326 int length = sizeof (int); 327 int *valuep = NULL; 328 uint64_t msi_addr_hi, msi_addr_lo; 329 330 DBG(DBG_MSIQ, dip, "px_msi_get_props\n"); 331 332 /* #msi */ 333 msi_state_p->msi_cnt = ddi_getprop(DDI_DEV_T_ANY, dip, 334 DDI_PROP_DONTPASS, "#msi", 0); 335 336 DBG(DBG_MSIQ, dip, "#msi=%d\n", msi_state_p->msi_cnt); 337 if (msi_state_p->msi_cnt == 0) 338 return (DDI_FAILURE); 339 340 /* msi-ranges: msi# field */ 341 if (ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_ALLOC, 342 DDI_PROP_DONTPASS, "msi-ranges", (caddr_t)&valuep, &length) 343 != DDI_PROP_SUCCESS) 344 return (DDI_FAILURE); 345 346 msi_state_p->msi_1st_msinum = ((px_msi_ranges_t *)valuep)->msi_no; 347 kmem_free(valuep, (size_t)length); 348 349 DBG(DBG_MSIQ, dip, "msi_1st_msinum=%d\n", msi_state_p->msi_1st_msinum); 350 351 /* msi-data-mask */ 352 msi_state_p->msi_data_mask = ddi_getprop(DDI_DEV_T_ANY, dip, 353 DDI_PROP_DONTPASS, "msi-data-mask", 0); 354 355 DBG(DBG_MSIQ, dip, "msi-data-mask=0x%x\n", 356 msi_state_p->msi_data_mask); 357 358 /* msi-data-width */ 359 msi_state_p->msi_data_width = ddi_getprop(DDI_DEV_T_ANY, dip, 360 DDI_PROP_DONTPASS, "msix-data-width", 0); 361 362 DBG(DBG_MSIQ, dip, "msix-data-width=%d\n", 363 msi_state_p->msi_data_width); 364 365 /* 366 * Assume MSI is always supported, but also check if MSIX is supported 367 */ 368 if (msi_state_p->msi_data_width) { 369 msi_state_p->msi_type = DDI_INTR_TYPE_MSI; 370 if (msi_state_p->msi_data_width == PX_MSIX_WIDTH) 371 msi_state_p->msi_type |= DDI_INTR_TYPE_MSIX; 372 } else { 373 return (DDI_FAILURE); 374 } 375 376 /* msi-address-ranges */ 377 if (ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_ALLOC, 378 DDI_PROP_DONTPASS, "msi-address-ranges", (caddr_t)&valuep, &length) 379 != DDI_PROP_SUCCESS) 380 return (DDI_FAILURE); 381 382 msi_addr_hi = ((px_msi_address_ranges_t *)valuep)->msi_addr32_hi; 383 msi_addr_lo = ((px_msi_address_ranges_t *)valuep)->msi_addr32_lo; 384 msi_state_p->msi_addr32 = (msi_addr_hi << 32) | msi_addr_lo; 385 msi_state_p->msi_addr32_len = 386 ((px_msi_address_ranges_t *)valuep)->msi_addr32_len; 387 388 msi_addr_hi = ((px_msi_address_ranges_t *)valuep)->msi_addr64_hi; 389 msi_addr_lo = ((px_msi_address_ranges_t *)valuep)->msi_addr64_lo; 390 msi_state_p->msi_addr64 = (msi_addr_hi << 32) | msi_addr_lo; 391 msi_state_p->msi_addr64_len = 392 ((px_msi_address_ranges_t *)valuep)->msi_addr64_len; 393 394 DBG(DBG_MSIQ, dip, "msi_addr32=0x%llx\n", msi_state_p->msi_addr32); 395 DBG(DBG_MSIQ, dip, "msi_addr64=0x%llx\n", msi_state_p->msi_addr64); 396 397 kmem_free(valuep, (size_t)length); 398 return (DDI_SUCCESS); 399 } 400