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 */
42uint_t		ddi_msix_alloc_limit = DDI_DEFAULT_MSIX_ALLOC;
43#endif
44
45/*
46 * New DDI interrupt framework
47 */
48void
49i_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
67void
68i_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
102uint_t
103i_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 */
124void
125i_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
133uint_t
134i_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 */
157void
158i_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
166uint_t
167i_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 */
178void
179i_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
187uint_t
188i_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 */
199void
200i_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
208uint_t
209i_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
216void
217i_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 */
232uint_t
233i_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 */
264uint_t
265i_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
309ddi_intr_msix_t *
310i_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
317void
318i_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
326ddi_intr_handle_t
327i_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
345void
346i_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 */
383int32_t
384i_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
395int32_t
396i_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 */
420ddi_intrspec_t
421i_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 */
434int
435i_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 */
452void
453i_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 */
465int
466i_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 */
482int
483get_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
513int
514set_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)
543ddi_acc_handle_t
544i_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
551void
552i_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
561int
562i_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
569void
570i_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