xref: /illumos-gate/usr/src/uts/sun4/io/px/px_msi.c (revision 56f33205c9ed776c3c909e07d52e94610a675740)
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