xref: /illumos-gate/usr/src/uts/common/os/ddi_intr_impl.c (revision 66582b606a8194f7f3ba5b3a3a6dca5b0d346361)
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 (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
23  */
24 
25 #include <sys/note.h>
26 #include <sys/sysmacros.h>
27 #include <sys/types.h>
28 #include <sys/param.h>
29 #include <sys/systm.h>
30 #include <sys/kmem.h>
31 #include <sys/cmn_err.h>
32 #include <sys/debug.h>
33 #include <sys/avintr.h>
34 #include <sys/autoconf.h>
35 #include <sys/sunndi.h>
36 #include <sys/ndi_impldefs.h>	/* include prototypes */
37 
38 #if defined(__i386) || defined(__amd64)
39 /*
40  * MSI-X allocation limit.
41  */
42 uint_t		ddi_msix_alloc_limit = DDI_DEFAULT_MSIX_ALLOC;
43 #endif
44 
45 /*
46  * New DDI interrupt framework
47  */
48 void
49 i_ddi_intr_devi_init(dev_info_t *dip)
50 {
51 	int	supported_types;
52 
53 	DDI_INTR_APIDBG((CE_CONT, "i_ddi_intr_devi_init: dip %p\n",
54 	    (void *)dip));
55 
56 	if (DEVI(dip)->devi_intr_p)
57 		return;
58 
59 	DEVI(dip)->devi_intr_p = kmem_zalloc(sizeof (devinfo_intr_t), KM_SLEEP);
60 
61 	supported_types = i_ddi_intr_get_supported_types(dip);
62 
63 	/* Save supported interrupt types information */
64 	i_ddi_intr_set_supported_types(dip, supported_types);
65 }
66 
67 void
68 i_ddi_intr_devi_fini(dev_info_t *dip)
69 {
70 	devinfo_intr_t	*intr_p = DEVI(dip)->devi_intr_p;
71 
72 	DDI_INTR_APIDBG((CE_CONT, "i_ddi_intr_devi_fini: dip %p\n",
73 	    (void *)dip));
74 
75 	if ((intr_p == NULL) || i_ddi_intr_get_current_nintrs(dip))
76 		return;
77 
78 	/*
79 	 * devi_intr_handle_p will only be used for devices
80 	 * which are using the legacy DDI Interrupt interfaces.
81 	 */
82 	if (intr_p->devi_intr_handle_p) {
83 		/* nintrs could be zero; so check for it first */
84 		if (intr_p->devi_intr_sup_nintrs) {
85 			kmem_free(intr_p->devi_intr_handle_p,
86 			    intr_p->devi_intr_sup_nintrs *
87 			    sizeof (ddi_intr_handle_t));
88 		}
89 	}
90 
91 	/*
92 	 * devi_irm_req_p will only be used for devices which
93 	 * are mapped to an Interrupt Resource Management pool.
94 	 */
95 	if (intr_p->devi_irm_req_p)
96 		(void) i_ddi_irm_remove(dip);
97 
98 	kmem_free(DEVI(dip)->devi_intr_p, sizeof (devinfo_intr_t));
99 	DEVI(dip)->devi_intr_p = NULL;
100 }
101 
102 uint_t
103 i_ddi_intr_get_supported_types(dev_info_t *dip)
104 {
105 	devinfo_intr_t		*intr_p = DEVI(dip)->devi_intr_p;
106 	ddi_intr_handle_impl_t	hdl;
107 	int			ret, intr_types;
108 
109 	if ((intr_p) && (intr_p->devi_intr_sup_types))
110 		return (intr_p->devi_intr_sup_types);
111 
112 	bzero(&hdl, sizeof (ddi_intr_handle_impl_t));
113 	hdl.ih_dip = dip;
114 
115 	ret = i_ddi_intr_ops(dip, dip, DDI_INTROP_SUPPORTED_TYPES, &hdl,
116 	    (void *)&intr_types);
117 
118 	return ((ret == DDI_SUCCESS) ? intr_types : 0);
119 }
120 
121 /*
122  * NOTE: This function is only called by i_ddi_dev_init().
123  */
124 void
125 i_ddi_intr_set_supported_types(dev_info_t *dip, int intr_types)
126 {
127 	devinfo_intr_t		*intr_p = DEVI(dip)->devi_intr_p;
128 
129 	if (intr_p)
130 		intr_p->devi_intr_sup_types = intr_types;
131 }
132 
133 uint_t
134 i_ddi_intr_get_supported_nintrs(dev_info_t *dip, int intr_type)
135 {
136 	devinfo_intr_t		*intr_p = DEVI(dip)->devi_intr_p;
137 	ddi_intr_handle_impl_t	hdl;
138 	int			ret, nintrs;
139 
140 	if ((intr_p) && (intr_p->devi_intr_curr_type == intr_type) &&
141 	    (intr_p->devi_intr_sup_nintrs))
142 		return (intr_p->devi_intr_sup_nintrs);
143 
144 	bzero(&hdl, sizeof (ddi_intr_handle_impl_t));
145 	hdl.ih_dip = dip;
146 	hdl.ih_type = intr_type;
147 
148 	ret = i_ddi_intr_ops(dip, dip, DDI_INTROP_NINTRS, &hdl,
149 	    (void *)&nintrs);
150 
151 	return ((ret == DDI_SUCCESS) ? nintrs : 0);
152 }
153 
154 /*
155  * NOTE: This function is only called by ddi_intr_alloc().
156  */
157 void
158 i_ddi_intr_set_supported_nintrs(dev_info_t *dip, int nintrs)
159 {
160 	devinfo_intr_t *intr_p = DEVI(dip)->devi_intr_p;
161 
162 	if (intr_p)
163 		intr_p->devi_intr_sup_nintrs = nintrs;
164 }
165 
166 uint_t
167 i_ddi_intr_get_current_type(dev_info_t *dip)
168 {
169 	devinfo_intr_t *intr_p = DEVI(dip)->devi_intr_p;
170 
171 	return (intr_p ? intr_p->devi_intr_curr_type : 0);
172 }
173 
174 /*
175  * NOTE: This function is only called by
176  *       ddi_intr_alloc() and ddi_intr_free().
177  */
178 void
179 i_ddi_intr_set_current_type(dev_info_t *dip, int intr_type)
180 {
181 	devinfo_intr_t *intr_p = DEVI(dip)->devi_intr_p;
182 
183 	if (intr_p)
184 		intr_p->devi_intr_curr_type = intr_type;
185 }
186 
187 uint_t
188 i_ddi_intr_get_current_nintrs(dev_info_t *dip)
189 {
190 	devinfo_intr_t *intr_p = DEVI(dip)->devi_intr_p;
191 
192 	return (intr_p ? intr_p->devi_intr_curr_nintrs : 0);
193 }
194 
195 /*
196  * NOTE: This function is only called by
197  *       ddi_intr_alloc() and ddi_intr_free().
198  */
199 void
200 i_ddi_intr_set_current_nintrs(dev_info_t *dip, int nintrs)
201 {
202 	devinfo_intr_t *intr_p = DEVI(dip)->devi_intr_p;
203 
204 	if (intr_p)
205 		intr_p->devi_intr_curr_nintrs = nintrs;
206 }
207 
208 uint_t
209 i_ddi_intr_get_current_nenables(dev_info_t *dip)
210 {
211 	devinfo_intr_t *intr_p = DEVI(dip)->devi_intr_p;
212 
213 	return (intr_p ? intr_p->devi_intr_curr_nenables : 0);
214 }
215 
216 void
217 i_ddi_intr_set_current_nenables(dev_info_t *dip, int nintrs)
218 {
219 	devinfo_intr_t *intr_p = DEVI(dip)->devi_intr_p;
220 
221 	if (intr_p)
222 		intr_p->devi_intr_curr_nenables = nintrs;
223 }
224 
225 /*
226  * i_ddi_intr_get_current_navail:
227  *
228  *	Return the number of interrupts currently available.
229  *	If a precise number set by IRM is not available, then
230  *	return the limit determined by i_ddi_intr_get_limit().
231  */
232 uint_t
233 i_ddi_intr_get_current_navail(dev_info_t *dip, int type)
234 {
235 	devinfo_intr_t		*intr_p;
236 	ddi_irm_pool_t		*pool_p;
237 	ddi_irm_req_t		*req_p;
238 	uint_t			navail;
239 
240 	/* Check for a precise number from IRM */
241 	if (((intr_p = DEVI(dip)->devi_intr_p) != NULL) &&
242 	    ((req_p = intr_p->devi_irm_req_p) != NULL) &&
243 	    (type == req_p->ireq_type) &&
244 	    ((pool_p = req_p->ireq_pool_p) != NULL)) {
245 		/*
246 		 * Lock to be sure a rebalance is not in progress.
247 		 * (Should be changed to a rwlock.)
248 		 */
249 		mutex_enter(&pool_p->ipool_navail_lock);
250 		navail = req_p->ireq_navail;
251 		mutex_exit(&pool_p->ipool_navail_lock);
252 		return (navail);
253 	}
254 
255 	/* Otherwise, return the limit */
256 	return (i_ddi_intr_get_limit(dip, type, NULL));
257 }
258 
259 /*
260  * i_ddi_intr_get_limit:
261  *
262  *	Return the limit of how many interrupts a driver can allocate.
263  */
264 uint_t
265 i_ddi_intr_get_limit(dev_info_t *dip, int type, ddi_irm_pool_t *pool_p)
266 {
267 	ddi_intr_handle_impl_t	hdl;
268 	uint_t			limit, nintrs;
269 
270 	/* Check for interrupt pool */
271 	if (pool_p == NULL)
272 		pool_p = i_ddi_intr_get_pool(dip, type);
273 
274 	/* Get default limit, from interrupt pool or by INTROP method */
275 	if (pool_p != NULL) {
276 		limit = pool_p->ipool_defsz;
277 	} else {
278 		bzero(&hdl, sizeof (ddi_intr_handle_impl_t));
279 		hdl.ih_dip = dip;
280 		hdl.ih_type = type;
281 		if (i_ddi_intr_ops(dip, dip, DDI_INTROP_NAVAIL, &hdl,
282 		    (void *)&limit) != DDI_SUCCESS)
283 			return (0);
284 	}
285 
286 	/* Get maximum supported by the device */
287 	nintrs = i_ddi_intr_get_supported_nintrs(dip, type);
288 
289 	/* No limit if device and system both support IRM */
290 	if ((pool_p != NULL) && (i_ddi_irm_supported(dip, type) == DDI_SUCCESS))
291 		return (nintrs);
292 
293 	/* Limit cannot exceed what device supports */
294 	limit = MIN(limit, nintrs);
295 
296 	/* Impose a global MSI-X limit on x86 */
297 #if defined(__i386) || defined(__amd64)
298 	if (type == DDI_INTR_TYPE_MSIX)
299 		limit = MIN(limit, ddi_msix_alloc_limit);
300 #endif
301 
302 	/* Impose a global MSI limit on all platforms */
303 	if (type == DDI_INTR_TYPE_MSI)
304 		limit = MIN(limit, DDI_MAX_MSI_ALLOC);
305 
306 	return (limit);
307 }
308 
309 ddi_intr_msix_t *
310 i_ddi_get_msix(dev_info_t *dip)
311 {
312 	devinfo_intr_t *intr_p = DEVI(dip)->devi_intr_p;
313 
314 	return (intr_p ? intr_p->devi_msix_p : NULL);
315 }
316 
317 void
318 i_ddi_set_msix(dev_info_t *dip, ddi_intr_msix_t *msix_p)
319 {
320 	devinfo_intr_t *intr_p = DEVI(dip)->devi_intr_p;
321 
322 	if (intr_p)
323 		intr_p->devi_msix_p = msix_p;
324 }
325 
326 ddi_intr_handle_t
327 i_ddi_get_intr_handle(dev_info_t *dip, int inum)
328 {
329 	devinfo_intr_t *intr_p = DEVI(dip)->devi_intr_p;
330 
331 	if (intr_p == NULL)
332 		return (NULL);
333 
334 	/*
335 	 * Changed this to a check and return NULL if an invalid inum
336 	 * is passed to retrieve a handle
337 	 */
338 	if ((inum < 0) || (inum >= intr_p->devi_intr_sup_nintrs))
339 		return (NULL);
340 
341 	return ((intr_p->devi_intr_handle_p) ?
342 	    intr_p->devi_intr_handle_p[inum] : NULL);
343 }
344 
345 void
346 i_ddi_set_intr_handle(dev_info_t *dip, int inum, ddi_intr_handle_t intr_hdl)
347 {
348 	devinfo_intr_t	*intr_p = DEVI(dip)->devi_intr_p;
349 
350 	if (intr_p == NULL)
351 		return;
352 
353 	/*
354 	 * Changed this to a check and return if an invalid inum
355 	 * is passed to set a handle
356 	 */
357 	if ((inum < 0) || (inum >= intr_p->devi_intr_sup_nintrs))
358 		return;
359 
360 	if (intr_hdl && (intr_p->devi_intr_handle_p == NULL)) {
361 		/* nintrs could be zero; so check for it first */
362 		if (intr_p->devi_intr_sup_nintrs)
363 			intr_p->devi_intr_handle_p = kmem_zalloc(
364 			    sizeof (ddi_intr_handle_t) *
365 			    intr_p->devi_intr_sup_nintrs, KM_SLEEP);
366 	}
367 
368 	if (intr_p->devi_intr_handle_p)
369 		intr_p->devi_intr_handle_p[inum] = intr_hdl;
370 }
371 
372 /*
373  * The "ddi-intr-weight" property contains the weight of each interrupt
374  * associated with a dev_info node. For devices with multiple interrupts per
375  * dev_info node, the total load of the device is "devi_intr_weight * nintr",
376  * possibly spread out over multiple CPUs.
377  *
378  * Maintaining this as a property permits possible tweaking in the product
379  * in response to customer problems via driver.conf property definitions at
380  * the driver or the instance level.  This does not mean that "ddi-intr_weight"
381  * is a formal or committed interface.
382  */
383 int32_t
384 i_ddi_get_intr_weight(dev_info_t *dip)
385 {
386 	int32_t	weight;
387 
388 	weight = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
389 	    DDI_PROP_DONTPASS | DDI_PROP_NOTPROM, "ddi-intr-weight", -1);
390 	if (weight < -1)
391 		weight = -1;			/* undefined */
392 	return (weight);
393 }
394 
395 int32_t
396 i_ddi_set_intr_weight(dev_info_t *dip, int32_t weight)
397 {
398 	int32_t oweight;
399 
400 	oweight = i_ddi_get_intr_weight(dip);
401 	if ((weight > 0) && (oweight != weight))
402 		(void) ndi_prop_update_int(DDI_DEV_T_NONE, dip,
403 		    "ddi-intr-weight", weight);
404 	return (oweight);
405 }
406 
407 /*
408  * Old DDI interrupt framework
409  *
410  * NOTE:
411  *	The following 4 busops entry points are obsoleted with version
412  *	9 or greater. Use i_ddi_intr_op interface in place of these
413  *	obsolete interfaces.
414  *
415  *	Remove these busops entry points and all related data structures
416  *	in future major/minor solaris release.
417  */
418 
419 /* ARGSUSED */
420 ddi_intrspec_t
421 i_ddi_get_intrspec(dev_info_t *dip, dev_info_t *rdip, uint_t inumber)
422 {
423 	dev_info_t	*pdip = ddi_get_parent(dip);
424 
425 	cmn_err(CE_WARN, "Failed to process interrupt "
426 	    "for %s%d due to down-rev nexus driver %s%d",
427 	    ddi_driver_name(rdip), ddi_get_instance(rdip),
428 	    ddi_driver_name(pdip), ddi_get_instance(pdip));
429 
430 	return (NULL);
431 }
432 
433 /* ARGSUSED */
434 int
435 i_ddi_add_intrspec(dev_info_t *dip, dev_info_t *rdip, ddi_intrspec_t intrspec,
436     ddi_iblock_cookie_t *iblock_cookiep,
437     ddi_idevice_cookie_t *idevice_cookiep,
438     uint_t (*int_handler)(caddr_t int_handler_arg),
439     caddr_t int_handler_arg, int kind)
440 {
441 	dev_info_t	*pdip = ddi_get_parent(dip);
442 
443 	cmn_err(CE_WARN, "Failed to process interrupt "
444 	    "for %s%d due to down-rev nexus driver %s%d",
445 	    ddi_driver_name(rdip), ddi_get_instance(rdip),
446 	    ddi_driver_name(pdip), ddi_get_instance(pdip));
447 
448 	return (DDI_ENOTSUP);
449 }
450 
451 /* ARGSUSED */
452 void
453 i_ddi_remove_intrspec(dev_info_t *dip, dev_info_t *rdip,
454     ddi_intrspec_t intrspec, ddi_iblock_cookie_t iblock_cookie)
455 {
456 	dev_info_t	*pdip = ddi_get_parent(dip);
457 
458 	cmn_err(CE_WARN, "Failed to process interrupt "
459 	    "for %s%d due to down-rev nexus driver %s%d",
460 	    ddi_driver_name(rdip), ddi_get_instance(rdip),
461 	    ddi_driver_name(pdip), ddi_get_instance(pdip));
462 }
463 
464 /* ARGSUSED */
465 int
466 i_ddi_intr_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_intr_ctlop_t op,
467     void *arg, void *val)
468 {
469 	dev_info_t	*pdip = ddi_get_parent(dip);
470 
471 	cmn_err(CE_WARN, "Failed to process interrupt "
472 	    "for %s%d due to down-rev nexus driver %s%d",
473 	    ddi_driver_name(rdip), ddi_get_instance(rdip),
474 	    ddi_driver_name(pdip), ddi_get_instance(pdip));
475 
476 	return (DDI_ENOTSUP);
477 }
478 
479 /*
480  * Interrupt target get/set functions
481  */
482 int
483 get_intr_affinity(ddi_intr_handle_t h, processorid_t *tgt_p)
484 {
485 	ddi_intr_handle_impl_t	*hdlp = (ddi_intr_handle_impl_t *)h;
486 	int			ret;
487 
488 	DDI_INTR_APIDBG((CE_CONT, "get_intr_affinity: hdlp = %p\n",
489 	    (void *)hdlp));
490 
491 	if ((hdlp == NULL) || (tgt_p == NULL))
492 		return (DDI_EINVAL);
493 
494 	rw_enter(&hdlp->ih_rwlock, RW_READER);
495 	if (hdlp->ih_state != DDI_IHDL_STATE_ENABLE) {
496 		rw_exit(&hdlp->ih_rwlock);
497 		return (DDI_EINVAL);
498 	}
499 
500 	ret = i_ddi_intr_ops(hdlp->ih_dip, hdlp->ih_dip,
501 	    DDI_INTROP_GETTARGET, hdlp, (void *)tgt_p);
502 
503 	DDI_INTR_APIDBG((CE_CONT, "get_intr_affinity: target %x\n",
504 	    *tgt_p));
505 
506 	if (ret == DDI_SUCCESS)
507 		hdlp->ih_target = *tgt_p;
508 
509 	rw_exit(&hdlp->ih_rwlock);
510 	return (ret);
511 }
512 
513 int
514 set_intr_affinity(ddi_intr_handle_t h, processorid_t tgt)
515 {
516 	ddi_intr_handle_impl_t	*hdlp = (ddi_intr_handle_impl_t *)h;
517 	int			ret;
518 
519 	DDI_INTR_APIDBG((CE_CONT, "set_intr_affinity: hdlp = %p "
520 	    "target %x\n", (void *)hdlp, tgt));
521 
522 	if (hdlp == NULL)
523 		return (DDI_EINVAL);
524 
525 	rw_enter(&hdlp->ih_rwlock, RW_WRITER);
526 	if ((hdlp->ih_state != DDI_IHDL_STATE_ENABLE) ||
527 	    (hdlp->ih_type != DDI_INTR_TYPE_MSIX)) {
528 		rw_exit(&hdlp->ih_rwlock);
529 		return (DDI_EINVAL);
530 	}
531 
532 	ret = i_ddi_intr_ops(hdlp->ih_dip, hdlp->ih_dip,
533 	    DDI_INTROP_SETTARGET, hdlp, &tgt);
534 
535 	if (ret == DDI_SUCCESS)
536 		hdlp->ih_target = tgt;
537 
538 	rw_exit(&hdlp->ih_rwlock);
539 	return (ret);
540 }
541 
542 #if defined(__i386) || defined(__amd64)
543 ddi_acc_handle_t
544 i_ddi_get_pci_config_handle(dev_info_t *dip)
545 {
546 	devinfo_intr_t *intr_p = DEVI(dip)->devi_intr_p;
547 
548 	return (intr_p ? intr_p->devi_cfg_handle : NULL);
549 }
550 
551 void
552 i_ddi_set_pci_config_handle(dev_info_t *dip, ddi_acc_handle_t handle)
553 {
554 	devinfo_intr_t *intr_p = DEVI(dip)->devi_intr_p;
555 
556 	if (intr_p)
557 		intr_p->devi_cfg_handle = handle;
558 }
559 
560 
561 int
562 i_ddi_get_msi_msix_cap_ptr(dev_info_t *dip)
563 {
564 	devinfo_intr_t *intr_p = DEVI(dip)->devi_intr_p;
565 
566 	return (intr_p ? intr_p->devi_cap_ptr : 0);
567 }
568 
569 void
570 i_ddi_set_msi_msix_cap_ptr(dev_info_t *dip, int cap_ptr)
571 {
572 	devinfo_intr_t *intr_p = DEVI(dip)->devi_intr_p;
573 
574 	if (intr_p)
575 		intr_p->devi_cap_ptr = cap_ptr;
576 }
577 #endif
578