xref: /illumos-gate/usr/src/uts/sun4/io/px/px_msi.c (revision c3ea2840)
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 	irm_params.iparams_default = DDI_DEFAULT_MSIX_ALLOC;
87 	if (ndi_irm_create(dip, &irm_params, &irm_pool_p) == DDI_SUCCESS) {
88 		msi_state_p->msi_pool_p = irm_pool_p;
89 	} else {
90 		DBG(DBG_MSIQ, dip, "ndi_irm_create() failed\n");
91 	}
92 
93 	if ((ret = px_lib_msi_init(dip)) != DDI_SUCCESS)
94 		px_msi_detach(px_p);
95 
96 	return (ret);
97 }
98 
99 
100 /*
101  * msi_detach()
102  */
103 void
104 px_msi_detach(px_t *px_p)
105 {
106 	dev_info_t	*dip = px_p->px_dip;
107 	px_msi_state_t	*msi_state_p = &px_p->px_ib_p->ib_msi_state;
108 
109 	DBG(DBG_MSIQ, dip, "px_msi_detach\n");
110 
111 	if (msi_state_p->msi_pool_p) {
112 		(void) ndi_irm_destroy(msi_state_p->msi_pool_p);
113 	}
114 
115 	if (msi_state_p->msi_addr64 && msi_state_p->msi_mem_flg) {
116 		ndi_ra_free(dip, msi_state_p->msi_addr64,
117 		    msi_state_p->msi_addr64_len,
118 		    NDI_RA_TYPE_MEM, NDI_RA_PASS);
119 	}
120 
121 	if (msi_state_p->msi_addr32 && msi_state_p->msi_mem_flg) {
122 		ndi_ra_free(dip, msi_state_p->msi_addr32,
123 		    msi_state_p->msi_addr32_len,
124 		    NDI_RA_TYPE_MEM, NDI_RA_PASS);
125 
126 		pci_resource_destroy(dip);
127 	}
128 
129 	if (msi_state_p->msi_p) {
130 		kmem_free(msi_state_p->msi_p,
131 		    msi_state_p->msi_cnt * sizeof (px_msi_t));
132 	}
133 
134 	mutex_destroy(&msi_state_p->msi_mutex);
135 	bzero(&px_p->px_ib_p->ib_msi_state, sizeof (px_msi_state_t));
136 }
137 
138 
139 /*
140  * msi_alloc()
141  */
142 /* ARGSUSED */
143 int
144 px_msi_alloc(px_t *px_p, dev_info_t *rdip, int type, int inum, int msi_count,
145     int flag, int *actual_msi_count_p)
146 {
147 	px_msi_state_t	*msi_state_p = &px_p->px_ib_p->ib_msi_state;
148 	int		first, count, i, n;
149 
150 	DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_alloc: rdip %s:%d "
151 	    "type 0x%x inum 0x%x msi_count 0x%x\n", ddi_driver_name(rdip),
152 	    ddi_get_instance(rdip), type, inum, msi_count);
153 
154 	mutex_enter(&msi_state_p->msi_mutex);
155 
156 	*actual_msi_count_p = 0;
157 
158 	/*
159 	 * MSI interrupts are allocated as contiguous ranges at
160 	 * power of 2 boundaries from the start of the MSI array.
161 	 */
162 	if (type == DDI_INTR_TYPE_MSI) {
163 
164 		/* Search for a range of available interrupts */
165 		for (count = msi_count; count; count >>= 1) {
166 			for (first = 0; (first + count) < msi_state_p->msi_cnt;
167 			    first += count) {
168 				for (i = first; i < (first + count); i++) {
169 					if (msi_state_p->msi_p[i].msi_state
170 					    != MSI_STATE_FREE) {
171 						break;
172 					}
173 				}
174 				if (i == (first + count)) {
175 					goto found_msi;
176 				}
177 			}
178 			DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_alloc: failed\n");
179 			if (count > 1) {
180 				DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_alloc: "
181 				    "Retry MSI allocation with new msi_count "
182 				    "0x%x\n", count >> 1);
183 			}
184 		}
185 
186 found_msi:
187 		/* Set number of available interrupts */
188 		*actual_msi_count_p = count;
189 
190 		/* Check if successful, and enforce strict behavior */
191 		if ((count == 0) ||
192 		    ((flag == DDI_INTR_ALLOC_STRICT) && (count != msi_count))) {
193 			mutex_exit(&msi_state_p->msi_mutex);
194 			return (DDI_EAGAIN);
195 		}
196 
197 		/* Allocate the interrupts */
198 		for (i = first; i < (first + count); i++, inum++) {
199 			msi_state_p->msi_p[i].msi_state = MSI_STATE_INUSE;
200 			msi_state_p->msi_p[i].msi_dip = rdip;
201 			msi_state_p->msi_p[i].msi_inum = inum;
202 		}
203 	}
204 
205 	/*
206 	 * MSI-X interrupts are allocated from the end of the MSI
207 	 * array.  There are no concerns about power of 2 boundaries
208 	 * and the allocated interrupts do not have to be contiguous.
209 	 */
210 	if (type == DDI_INTR_TYPE_MSIX) {
211 
212 		/* Count available interrupts, up to count requested */
213 		for (count = 0, i = (msi_state_p->msi_cnt - 1); i >= 0; i--) {
214 			if (msi_state_p->msi_p[i].msi_state == MSI_STATE_FREE) {
215 				if (count == 0)
216 					first = i;
217 				count++;
218 				if (count == msi_count)
219 					break;
220 			}
221 		}
222 
223 		/* Set number of available interrupts */
224 		*actual_msi_count_p = count;
225 
226 		/* Check if successful, and enforce strict behavior */
227 		if ((count == 0) ||
228 		    ((flag == DDI_INTR_ALLOC_STRICT) && (count != msi_count))) {
229 			mutex_exit(&msi_state_p->msi_mutex);
230 			return (DDI_EAGAIN);
231 		}
232 
233 		/* Allocate the interrupts */
234 		for (n = 0, i = first; n < count; i--) {
235 			if (msi_state_p->msi_p[i].msi_state != MSI_STATE_FREE)
236 				continue;
237 			msi_state_p->msi_p[i].msi_state = MSI_STATE_INUSE;
238 			msi_state_p->msi_p[i].msi_dip = rdip;
239 			msi_state_p->msi_p[i].msi_inum = inum;
240 			inum++;
241 			n++;
242 		}
243 	}
244 
245 	DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_alloc: rdip %s:%d "
246 	    "msi_num 0x%x count 0x%x\n", ddi_driver_name(rdip),
247 	    ddi_get_instance(rdip), first, count);
248 
249 	mutex_exit(&msi_state_p->msi_mutex);
250 
251 	return (DDI_SUCCESS);
252 }
253 
254 
255 /*
256  * msi_free()
257  */
258 int
259 px_msi_free(px_t *px_p, dev_info_t *rdip, int inum, int msi_count)
260 {
261 	px_msi_state_t	*msi_state_p = &px_p->px_ib_p->ib_msi_state;
262 	int		i, n;
263 
264 	DBG(DBG_R_MSIX, px_p->px_dip, "px_msi_free: rdip 0x%p "
265 	    "inum 0x%x msi_count 0x%x\n", rdip, inum, msi_count);
266 
267 	mutex_enter(&msi_state_p->msi_mutex);
268 
269 	/*
270 	 * Find and release the specified MSI/X numbers.
271 	 *
272 	 * Because the allocations are not always contiguous, perform
273 	 * a full linear search of the MSI/X table looking for MSI/X
274 	 * vectors owned by the device with inum values in the range
275 	 * [inum .. (inum + msi_count - 1)].
276 	 */
277 	for (i = 0, n = 0; (i < msi_state_p->msi_cnt) && (n < msi_count); i++) {
278 		if ((msi_state_p->msi_p[i].msi_dip == rdip) &&
279 		    (msi_state_p->msi_p[i].msi_inum >= inum) &&
280 		    (msi_state_p->msi_p[i].msi_inum < (inum + msi_count))) {
281 			msi_state_p->msi_p[i].msi_dip = NULL;
282 			msi_state_p->msi_p[i].msi_inum = 0;
283 			msi_state_p->msi_p[i].msi_msiq_id = 0;
284 			msi_state_p->msi_p[i].msi_state = MSI_STATE_FREE;
285 			n++;
286 		}
287 	}
288 
289 	mutex_exit(&msi_state_p->msi_mutex);
290 
291 	/* Fail if the MSI/X numbers were not found */
292 	if (n < msi_count)
293 		return (DDI_FAILURE);
294 
295 	return (DDI_SUCCESS);
296 }
297 
298 /*
299  * msi_get_msinum()
300  */
301 int
302 px_msi_get_msinum(px_t *px_p, dev_info_t *rdip, int inum, msinum_t *msi_num_p)
303 {
304 	px_msi_state_t	*msi_state_p = &px_p->px_ib_p->ib_msi_state;
305 	int		i;
306 
307 	DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_get_msinum: "
308 	    "rdip 0x%p inum 0x%x\n", rdip, inum);
309 
310 	mutex_enter(&msi_state_p->msi_mutex);
311 
312 	for (i = 0; i < msi_state_p->msi_cnt; i++) {
313 		if ((msi_state_p->msi_p[i].msi_inum == inum) &&
314 		    (msi_state_p->msi_p[i].msi_dip == rdip)) {
315 
316 			*msi_num_p = msi_state_p->msi_p[i].msi_msinum;
317 
318 			DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_get_msinum: "
319 			    "inum 0x%x msi 0x%x\n", inum, *msi_num_p);
320 
321 			mutex_exit(&msi_state_p->msi_mutex);
322 			return (DDI_SUCCESS);
323 		}
324 	}
325 
326 	if (i >= msi_state_p->msi_cnt)
327 		DBG(DBG_A_MSIX, px_p->px_dip, "px_msi_get_msinum: "
328 		    "no msi for inum 0x%x\n", inum);
329 
330 	mutex_exit(&msi_state_p->msi_mutex);
331 	return (DDI_FAILURE);
332 }
333 
334 /*
335  * px_msi_get_props()
336  */
337 static int
338 px_msi_get_props(px_t *px_p)
339 {
340 	dev_info_t		*dip = px_p->px_dip;
341 	px_msi_state_t		*msi_state_p = &px_p->px_ib_p->ib_msi_state;
342 	int			ret = DDI_SUCCESS;
343 	int			length = sizeof (int);
344 	int			*valuep = NULL;
345 	uint64_t		msi_addr_hi, msi_addr_lo;
346 	uint64_t		mem_answer, mem_alen;
347 	ndi_ra_request_t	request;
348 
349 	DBG(DBG_MSIQ, dip, "px_msi_get_props\n");
350 
351 	/* #msi */
352 	msi_state_p->msi_cnt = ddi_getprop(DDI_DEV_T_ANY, dip,
353 	    DDI_PROP_DONTPASS, "#msi", PX_DEFAULT_MSI_CNT);
354 
355 	DBG(DBG_MSIQ, dip, "obp: #msi=%d\n",
356 	    msi_state_p->msi_cnt);
357 
358 	/* msi-ranges: msi# field */
359 	ret = ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_ALLOC,
360 	    DDI_PROP_DONTPASS, "msi-ranges", (caddr_t)&valuep, &length);
361 
362 	if (ret == DDI_PROP_SUCCESS) {
363 		msi_state_p->msi_1st_msinum =
364 		    ((px_msi_ranges_t *)valuep)->msi_no;
365 		kmem_free(valuep, (size_t)length);
366 	} else
367 		msi_state_p->msi_1st_msinum = PX_DEFAULT_MSI_1ST_MSINUM;
368 
369 	DBG(DBG_MSIQ, dip, "obp: msi_1st_msinum=%d\n",
370 	    msi_state_p->msi_1st_msinum);
371 
372 	/* msi-data-mask */
373 	msi_state_p->msi_data_mask = ddi_getprop(DDI_DEV_T_ANY, dip,
374 	    DDI_PROP_DONTPASS, "msi-data-mask", PX_DEFAULT_MSI_DATA_MASK);
375 
376 	DBG(DBG_MSIQ, dip, "obp: msi-data-mask=0x%x\n",
377 	    msi_state_p->msi_data_mask);
378 
379 	/* msi-data-width */
380 	msi_state_p->msi_data_width = ddi_getprop(DDI_DEV_T_ANY, dip,
381 	    DDI_PROP_DONTPASS, "msix-data-width", PX_DEFAULT_MSI_DATA_WIDTH);
382 
383 	DBG(DBG_MSIQ, dip, "obp: msix-data-width=%d\n",
384 	    msi_state_p->msi_data_width);
385 
386 	/*
387 	 * Assume MSI is always supported, but also check if MSIX is supported
388 	 */
389 	if (msi_state_p->msi_data_width) {
390 		msi_state_p->msi_type = DDI_INTR_TYPE_MSI;
391 		if (msi_state_p->msi_data_width == PX_MSIX_WIDTH)
392 			msi_state_p->msi_type |= DDI_INTR_TYPE_MSIX;
393 	}
394 
395 	/* msi-address-ranges */
396 	ret = ddi_prop_op(DDI_DEV_T_ANY, dip, PROP_LEN_AND_VAL_ALLOC,
397 	    DDI_PROP_DONTPASS, "msi-address-ranges", (caddr_t)&valuep,
398 	    &length);
399 
400 	if (ret == DDI_PROP_SUCCESS) {
401 		msi_addr_hi =
402 		    ((px_msi_address_ranges_t *)valuep)->msi_addr32_hi;
403 		msi_addr_lo =
404 		    ((px_msi_address_ranges_t *)valuep)->msi_addr32_lo;
405 		msi_state_p->msi_addr32 =
406 		    (msi_addr_hi << 32) | msi_addr_lo;
407 
408 		msi_state_p->msi_addr32_len =
409 		    ((px_msi_address_ranges_t *)valuep)->msi_addr32_len;
410 
411 		msi_addr_hi =
412 		    ((px_msi_address_ranges_t *)valuep)->msi_addr64_hi;
413 		msi_addr_lo =
414 		    ((px_msi_address_ranges_t *)valuep)->msi_addr64_lo;
415 		msi_state_p->msi_addr64 =
416 		    (msi_addr_hi << 32) | msi_addr_lo;
417 
418 		msi_state_p->msi_addr64_len =
419 		    ((px_msi_address_ranges_t *)valuep)->msi_addr64_len;
420 
421 		kmem_free(valuep, (size_t)length);
422 
423 		msi_state_p->msi_mem_flg = B_FALSE;
424 
425 		DBG(DBG_MSIQ, dip, "obp: msi_addr32=0x%llx\n",
426 		    msi_state_p->msi_addr32);
427 
428 		DBG(DBG_MSIQ, dip, "obp: msi_addr64=0x%llx\n",
429 		    msi_state_p->msi_addr64);
430 
431 		return (ret);
432 	}
433 
434 	/*
435 	 * If msi-address-ranges property does not exist in OBP, Fire
436 	 * driver will need to allocate memory.
437 	 *
438 	 * Allocate 64KB of memory from unused PCI-E address space for the MSI
439 	 * transactions and program MSI 32-bit address register.
440 	 *
441 	 * This register is used by the Fire hardware to compare against the
442 	 * address of incoming PCI-E 32-bit addressed memory write commands.
443 	 * If the address matches bits 31:16 then PCI-E command is considered
444 	 * to be MSI transaction.
445 	 *
446 	 * pci_resource_setup() is called in context of PCI hotplug
447 	 * initialization.
448 	 *
449 	 * Setup resource maps for this bus node.
450 	 */
451 	if (pci_resource_setup(dip) != NDI_SUCCESS) {
452 		DBG(DBG_MSIQ, dip, "px_msi_getprops: dip=%s%d"
453 		    "pci_resource_setup failed\n",
454 		    ddi_driver_name(dip), ddi_get_instance(dip));
455 
456 		return (DDI_FAILURE);
457 	}
458 
459 	msi_state_p->msi_mem_flg = B_TRUE;
460 
461 	/*
462 	 * Reserve PCI MEM 32 resources to perform 32 bit MSI transactions.
463 	 */
464 	bzero((caddr_t)&request, sizeof (ndi_ra_request_t));
465 	request.ra_flags = NDI_RA_ALLOC_BOUNDED;
466 	request.ra_boundbase = 0;
467 	request.ra_boundlen = PX_MSI_4GIG_LIMIT;
468 	request.ra_len = PX_MSI_ADDR_LEN;
469 	request.ra_align_mask = 0;
470 
471 	if (ndi_ra_alloc(dip, &request, &mem_answer, &mem_alen,
472 	    NDI_RA_TYPE_MEM, NDI_RA_PASS) != NDI_SUCCESS) {
473 		DBG(DBG_MSIQ, dip, "px_msi_getprops: Failed to allocate "
474 		    "64KB mem\n");
475 
476 		return (DDI_FAILURE);
477 	}
478 
479 	msi_state_p->msi_addr32 = mem_answer;
480 	msi_state_p->msi_addr32_len = mem_alen;
481 
482 	DBG(DBG_MSIQ, dip, "px_msi_getprops: 32 Addr 0x%llx\n",
483 	    msi_state_p->msi_addr32);
484 
485 	/*
486 	 * Reserve PCI MEM 64 resources to perform 64 bit MSI transactions.
487 	 *
488 	 * NOTE:
489 	 *
490 	 * Currently OBP do not export any "available" property or range in
491 	 * the MEM64 space. Hence ndi_ra_alloc() request will return failure.
492 	 * So, for time being ignore this failure.
493 	 */
494 	bzero((caddr_t)&request, sizeof (ndi_ra_request_t));
495 	request.ra_flags = NDI_RA_ALLOC_BOUNDED;
496 	request.ra_boundbase = PX_MSI_4GIG_LIMIT + 1;
497 	request.ra_boundlen = PX_MSI_4GIG_LIMIT;
498 	request.ra_len = PX_MSI_ADDR_LEN;
499 	request.ra_align_mask = 0;
500 
501 	if (ndi_ra_alloc(dip, &request, &mem_answer, &mem_alen,
502 	    NDI_RA_TYPE_MEM, NDI_RA_PASS) != NDI_SUCCESS) {
503 		DBG(DBG_MSIQ, dip, "px_msi_getprops: Failed to allocate "
504 		    "64KB mem\n");
505 
506 		return (DDI_SUCCESS);
507 	}
508 
509 	msi_state_p->msi_addr64 = mem_answer;
510 	msi_state_p->msi_addr64_len = mem_alen;
511 
512 	DBG(DBG_MSIQ, dip, "px_msi_getprops: 64 Addr 0x%llx\n",
513 	    msi_state_p->msi_addr64);
514 
515 	return (DDI_SUCCESS);
516 }
517