Home | History | Annotate | Download | only in px
      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