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