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) 2009, 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/ddi.h>
34 #include <sys/sunndi.h>
35 #include <sys/ndi_impldefs.h> /* include prototypes */
36
37 #if defined(__x86)
38 /*
39 * MSI-X allocation limit.
40 */
41 extern uint_t ddi_msix_alloc_limit;
42 #endif
43
44 /*
45 * Interrupt Resource Management (IRM).
46 */
47
48 #define DDI_IRM_BALANCE_DELAY (60) /* In seconds */
49
50 #define DDI_IRM_HAS_CB(c) ((c) && (c->cb_flags & DDI_CB_FLAG_INTR))
51
52 #define DDI_IRM_IS_REDUCIBLE(r) (((r->ireq_flags & DDI_IRM_FLAG_CALLBACK) && \
53 (r->ireq_type == DDI_INTR_TYPE_MSIX)) || \
54 (r->ireq_flags & DDI_IRM_FLAG_NEW))
55
56 extern pri_t minclsyspri;
57
58 /* Global policies */
59 int irm_enable = 1;
60 boolean_t irm_active = B_FALSE;
61 int irm_default_policy = DDI_IRM_POLICY_LARGE;
62 uint_t irm_balance_delay = DDI_IRM_BALANCE_DELAY;
63
64 /* Global list of interrupt pools */
65 kmutex_t irm_pools_lock;
66 list_t irm_pools_list;
67
68 /* Global debug tunables */
69 #ifdef DEBUG
70 int irm_debug_policy = 0;
71 uint_t irm_debug_size = 0;
72 #endif /* DEBUG */
73
74 static void irm_balance_thread(ddi_irm_pool_t *);
75 static void i_ddi_irm_balance(ddi_irm_pool_t *);
76 static void i_ddi_irm_enqueue(ddi_irm_pool_t *, boolean_t);
77 static void i_ddi_irm_reduce(ddi_irm_pool_t *pool);
78 static int i_ddi_irm_reduce_by_policy(ddi_irm_pool_t *, int, int);
79 static void i_ddi_irm_reduce_new(ddi_irm_pool_t *, int);
80 static void i_ddi_irm_insertion_sort(list_t *, ddi_irm_req_t *);
81 static int i_ddi_irm_notify(ddi_irm_pool_t *, ddi_irm_req_t *);
82 static int i_ddi_irm_modify_increase(ddi_irm_req_t *, int);
83
84 /*
85 * OS Initialization Routines
86 */
87
88 /*
89 * irm_init()
90 *
91 * Initialize IRM subsystem before any drivers are attached.
92 */
93 void
irm_init(void)94 irm_init(void)
95 {
96 /* Do nothing if IRM is disabled */
97 if (!irm_enable)
98 return;
99
100 /* Verify that the default balancing policy is valid */
101 if (!DDI_IRM_POLICY_VALID(irm_default_policy))
102 irm_default_policy = DDI_IRM_POLICY_LARGE;
103
104 /* Initialize the global list of interrupt pools */
105 mutex_init(&irm_pools_lock, NULL, MUTEX_DRIVER, NULL);
106 list_create(&irm_pools_list, sizeof (ddi_irm_pool_t),
107 offsetof(ddi_irm_pool_t, ipool_link));
108 }
109
110 /*
111 * i_ddi_irm_poststartup()
112 *
113 * IRM is not activated until after the IO subsystem is initialized.
114 * When activated, per-pool balancing threads are spawned and a flag
115 * is set so that all future pools will be activated when created.
116 *
117 * NOTE: the global variable 'irm_enable' disables IRM if zero.
118 */
119 void
i_ddi_irm_poststartup(void)120 i_ddi_irm_poststartup(void)
121 {
122 ddi_irm_pool_t *pool_p;
123
124 /* Do nothing if IRM is disabled */
125 if (!irm_enable)
126 return;
127
128 /* Lock the global list */
129 mutex_enter(&irm_pools_lock);
130
131 /* Activate all defined pools */
132 for (pool_p = list_head(&irm_pools_list); pool_p;
133 pool_p = list_next(&irm_pools_list, pool_p))
134 pool_p->ipool_thread = thread_create(NULL, 0,
135 irm_balance_thread, pool_p, 0, &p0, TS_RUN, minclsyspri);
136
137 /* Set future pools to be active */
138 irm_active = B_TRUE;
139
140 /* Unlock the global list */
141 mutex_exit(&irm_pools_lock);
142 }
143
144 /*
145 * NDI interfaces for creating/destroying IRM pools.
146 */
147
148 /*
149 * ndi_irm_create()
150 *
151 * Nexus interface to create an IRM pool. Create the new
152 * pool and add it to the global list of interrupt pools.
153 */
154 int
ndi_irm_create(dev_info_t * dip,ddi_irm_params_t * paramsp,ddi_irm_pool_t ** pool_retp)155 ndi_irm_create(dev_info_t *dip, ddi_irm_params_t *paramsp,
156 ddi_irm_pool_t **pool_retp)
157 {
158 ddi_irm_pool_t *pool_p;
159
160 ASSERT(dip != NULL);
161 ASSERT(paramsp != NULL);
162 ASSERT(pool_retp != NULL);
163 ASSERT(paramsp->iparams_total >= 1);
164 ASSERT(paramsp->iparams_types != 0);
165
166 DDI_INTR_IRMDBG((CE_CONT, "ndi_irm_create: dip %p\n", (void *)dip));
167
168 /* Check if IRM is enabled */
169 if (!irm_enable)
170 return (NDI_FAILURE);
171
172 /* Validate parameters */
173 if ((dip == NULL) || (paramsp == NULL) || (pool_retp == NULL) ||
174 (paramsp->iparams_total < 1) || (paramsp->iparams_types == 0))
175 return (NDI_FAILURE);
176
177 /* Allocate and initialize the pool */
178 pool_p = kmem_zalloc(sizeof (ddi_irm_pool_t), KM_SLEEP);
179 pool_p->ipool_owner = dip;
180 pool_p->ipool_policy = irm_default_policy;
181 pool_p->ipool_types = paramsp->iparams_types;
182 pool_p->ipool_totsz = paramsp->iparams_total;
183 pool_p->ipool_defsz = MIN(DDI_MAX_MSIX_ALLOC, MAX(DDI_MIN_MSIX_ALLOC,
184 paramsp->iparams_total / DDI_MSIX_ALLOC_DIVIDER));
185 list_create(&pool_p->ipool_req_list, sizeof (ddi_irm_req_t),
186 offsetof(ddi_irm_req_t, ireq_link));
187 list_create(&pool_p->ipool_scratch_list, sizeof (ddi_irm_req_t),
188 offsetof(ddi_irm_req_t, ireq_scratch_link));
189 cv_init(&pool_p->ipool_cv, NULL, CV_DRIVER, NULL);
190 mutex_init(&pool_p->ipool_lock, NULL, MUTEX_DRIVER, NULL);
191 mutex_init(&pool_p->ipool_navail_lock, NULL, MUTEX_DRIVER, NULL);
192
193 /* Add to global list of pools */
194 mutex_enter(&irm_pools_lock);
195 list_insert_tail(&irm_pools_list, pool_p);
196 mutex_exit(&irm_pools_lock);
197
198 /* If IRM is active, then activate the pool */
199 if (irm_active)
200 pool_p->ipool_thread = thread_create(NULL, 0,
201 irm_balance_thread, pool_p, 0, &p0, TS_RUN, minclsyspri);
202
203 *pool_retp = pool_p;
204 return (NDI_SUCCESS);
205 }
206
207 /*
208 * ndi_irm_resize_pool()
209 *
210 * Nexus interface to resize IRM pool. If the pool size drops
211 * below the allocated number of vectors then initiate rebalance
212 * operation before resizing the pool. If rebalance operation fails
213 * then return NDI_FAILURE.
214 */
215 int
ndi_irm_resize_pool(ddi_irm_pool_t * pool_p,uint_t new_size)216 ndi_irm_resize_pool(ddi_irm_pool_t *pool_p, uint_t new_size)
217 {
218 uint_t prev_size;
219
220 ASSERT(pool_p != NULL);
221
222 DDI_INTR_IRMDBG((CE_CONT, "ndi_irm_resize_pool: pool_p %p"
223 " current-size 0x%x new-size 0x%x\n",
224 (void *)pool_p, pool_p->ipool_totsz, new_size));
225
226 if (pool_p == NULL)
227 return (NDI_EINVAL);
228
229 /* Check if IRM is enabled */
230 if (!irm_enable)
231 return (NDI_FAILURE);
232
233 mutex_enter(&pool_p->ipool_lock);
234
235 /*
236 * If we are increasing the pool size or if the reserved
237 * number of vectors is <= the new pool size then simply
238 * update the pool size and enqueue a reblance operation
239 * if necessary to use the new vectors.
240 */
241 if ((pool_p->ipool_totsz < new_size) ||
242 (pool_p->ipool_resno <= new_size)) {
243 /* set new pool size */
244 pool_p->ipool_totsz = new_size;
245 /* adjust the default allocation limit */
246 pool_p->ipool_defsz = MIN(DDI_MAX_MSIX_ALLOC,
247 MAX(DDI_MIN_MSIX_ALLOC, new_size / DDI_MSIX_ALLOC_DIVIDER));
248 /* queue a rebalance operation to use the new vectors */
249 if (pool_p->ipool_reqno > pool_p->ipool_resno)
250 i_ddi_irm_enqueue(pool_p, B_FALSE);
251 mutex_exit(&pool_p->ipool_lock);
252 return (NDI_SUCCESS);
253 }
254
255 DDI_INTR_IRMDBG((CE_CONT, "ndi_irm_resize_pool: pool_p %p"
256 " needs a rebalance operation\n", (void *)pool_p));
257
258 /*
259 * requires a rebalance operation
260 */
261 /* save the current pool size */
262 prev_size = pool_p->ipool_totsz;
263 /* set the pool size to the desired new value */
264 pool_p->ipool_totsz = new_size;
265 /* perform the rebalance operation */
266 i_ddi_irm_enqueue(pool_p, B_TRUE);
267
268 /*
269 * If rebalance operation couldn't free up enough
270 * vectors then fail the resize operation.
271 */
272 if (pool_p->ipool_resno > new_size) { /* rebalance failed */
273 /* restore the pool size to the previous value */
274 pool_p->ipool_totsz = prev_size;
275 /* enqueue a rebalance operation for the original pool size */
276 i_ddi_irm_enqueue(pool_p, B_FALSE);
277 mutex_exit(&pool_p->ipool_lock);
278 return (NDI_FAILURE);
279 } else { /* rebalance worked */
280 /* adjust the default allocation limit */
281 pool_p->ipool_defsz = MIN(DDI_MAX_MSIX_ALLOC,
282 MAX(DDI_MIN_MSIX_ALLOC, new_size / DDI_MSIX_ALLOC_DIVIDER));
283 mutex_exit(&pool_p->ipool_lock);
284 DDI_INTR_IRMDBG((CE_CONT, "ndi_irm_resize_pool: pool_p %p"
285 " resized from %x to %x\n",
286 (void *)pool_p, prev_size, pool_p->ipool_totsz));
287 return (NDI_SUCCESS);
288 }
289 }
290
291 /*
292 * ndi_irm_destroy()
293 *
294 * Nexus interface to destroy an IRM pool. Destroy the pool
295 * and remove it from the global list of interrupt pools.
296 */
297 int
ndi_irm_destroy(ddi_irm_pool_t * pool_p)298 ndi_irm_destroy(ddi_irm_pool_t *pool_p)
299 {
300 ASSERT(pool_p != NULL);
301 ASSERT(pool_p->ipool_resno == 0);
302
303 DDI_INTR_IRMDBG((CE_CONT, "ndi_irm_destroy: pool_p %p\n",
304 (void *)pool_p));
305
306 /* Validate parameters */
307 if (pool_p == NULL)
308 return (NDI_FAILURE);
309
310 /* Validate that pool is empty */
311 if (pool_p->ipool_resno != 0)
312 return (NDI_BUSY);
313
314 /* Remove the pool from the global list */
315 mutex_enter(&irm_pools_lock);
316 list_remove(&irm_pools_list, pool_p);
317 mutex_exit(&irm_pools_lock);
318
319 /* Terminate the balancing thread */
320 mutex_enter(&pool_p->ipool_lock);
321 if (pool_p->ipool_thread &&
322 (pool_p->ipool_flags & DDI_IRM_FLAG_ACTIVE)) {
323 pool_p->ipool_flags |= DDI_IRM_FLAG_EXIT;
324 cv_signal(&pool_p->ipool_cv);
325 mutex_exit(&pool_p->ipool_lock);
326 thread_join(pool_p->ipool_thread->t_did);
327 } else
328 mutex_exit(&pool_p->ipool_lock);
329
330 /* Destroy the pool */
331 cv_destroy(&pool_p->ipool_cv);
332 mutex_destroy(&pool_p->ipool_lock);
333 mutex_destroy(&pool_p->ipool_navail_lock);
334 list_destroy(&pool_p->ipool_req_list);
335 list_destroy(&pool_p->ipool_scratch_list);
336 kmem_free(pool_p, sizeof (ddi_irm_pool_t));
337
338 return (NDI_SUCCESS);
339 }
340
341 /*
342 * Insert/Modify/Remove Interrupt Requests
343 */
344
345 /*
346 * i_ddi_irm_insert()
347 *
348 * Insert a new request into an interrupt pool, and balance the pool.
349 */
350 int
i_ddi_irm_insert(dev_info_t * dip,int type,int count)351 i_ddi_irm_insert(dev_info_t *dip, int type, int count)
352 {
353 ddi_irm_req_t *req_p;
354 devinfo_intr_t *intr_p;
355 ddi_irm_pool_t *pool_p;
356 uint_t nreq, nmin, npartial;
357 boolean_t irm_flag = B_FALSE;
358
359 ASSERT(dip != NULL);
360 ASSERT(DDI_INTR_TYPE_FLAG_VALID(type));
361 ASSERT(count > 0);
362
363 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_insert: dip %p type %d count %d\n",
364 (void *)dip, type, count));
365
366 /* Validate parameters */
367 if ((dip == NULL) || (count < 1) || !DDI_INTR_TYPE_FLAG_VALID(type)) {
368 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_insert: invalid args\n"));
369 return (DDI_EINVAL);
370 }
371
372 /* Check for an existing request */
373 if (((intr_p = DEVI(dip)->devi_intr_p) != NULL) &&
374 (intr_p->devi_irm_req_p != NULL))
375 return (DDI_SUCCESS);
376
377 /* Check for IRM support from the system */
378 if ((pool_p = i_ddi_intr_get_pool(dip, type)) == NULL) {
379 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_insert: not supported\n"));
380 return (DDI_ENOTSUP);
381 }
382
383 /* Check for IRM support from the driver */
384 if (i_ddi_irm_supported(dip, type) == DDI_SUCCESS)
385 irm_flag = B_TRUE;
386
387 /* Determine request size */
388 nreq = (irm_flag) ? count :
389 MIN(count, i_ddi_intr_get_limit(dip, type, pool_p));
390 nmin = (irm_flag) ? 1 : nreq;
391 npartial = MIN(nreq, pool_p->ipool_defsz);
392
393 /* Allocate and initialize the request */
394 req_p = kmem_zalloc(sizeof (ddi_irm_req_t), KM_SLEEP);
395 req_p->ireq_type = type;
396 req_p->ireq_dip = dip;
397 req_p->ireq_pool_p = pool_p;
398 req_p->ireq_nreq = nreq;
399 req_p->ireq_flags = DDI_IRM_FLAG_NEW;
400 if (irm_flag)
401 req_p->ireq_flags |= DDI_IRM_FLAG_CALLBACK;
402
403 /* Lock the pool */
404 mutex_enter(&pool_p->ipool_lock);
405
406 /* Check for minimal fit before inserting */
407 if ((pool_p->ipool_minno + nmin) > pool_p->ipool_totsz) {
408 cmn_err(CE_WARN, "%s%d: interrupt pool too full.\n",
409 ddi_driver_name(dip), ddi_get_instance(dip));
410 mutex_exit(&pool_p->ipool_lock);
411 kmem_free(req_p, sizeof (ddi_irm_req_t));
412 return (DDI_EAGAIN);
413 }
414
415 /* Insert the request into the pool */
416 pool_p->ipool_reqno += nreq;
417 pool_p->ipool_minno += nmin;
418 i_ddi_irm_insertion_sort(&pool_p->ipool_req_list, req_p);
419
420 /*
421 * Try to fulfill the request.
422 *
423 * If all the interrupts are available, and either the request
424 * is static or the pool is active, then just take them directly.
425 *
426 * If only some of the interrupts are available, and the request
427 * can receive future callbacks, then take some now but queue the
428 * pool to be rebalanced later.
429 *
430 * Otherwise, immediately rebalance the pool and wait.
431 */
432 if ((!irm_flag || (pool_p->ipool_flags & DDI_IRM_FLAG_ACTIVE)) &&
433 ((pool_p->ipool_resno + nreq) <= pool_p->ipool_totsz)) {
434
435 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_insert: "
436 "request completely fulfilled.\n"));
437 pool_p->ipool_resno += nreq;
438 req_p->ireq_navail = nreq;
439 req_p->ireq_flags &= ~(DDI_IRM_FLAG_NEW);
440
441 } else if (irm_flag &&
442 ((pool_p->ipool_resno + npartial) <= pool_p->ipool_totsz)) {
443
444 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_insert: "
445 "request partially fulfilled.\n"));
446 pool_p->ipool_resno += npartial;
447 req_p->ireq_navail = npartial;
448 req_p->ireq_flags &= ~(DDI_IRM_FLAG_NEW);
449 i_ddi_irm_enqueue(pool_p, B_FALSE);
450
451 } else {
452
453 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_insert: "
454 "request needs immediate rebalance.\n"));
455 i_ddi_irm_enqueue(pool_p, B_TRUE);
456 req_p->ireq_flags &= ~(DDI_IRM_FLAG_NEW);
457 }
458
459 /* Fail if the request cannot be fulfilled at all */
460 if (req_p->ireq_navail == 0) {
461 cmn_err(CE_WARN, "%s%d: interrupt pool too full.\n",
462 ddi_driver_name(dip), ddi_get_instance(dip));
463 pool_p->ipool_reqno -= nreq;
464 pool_p->ipool_minno -= nmin;
465 list_remove(&pool_p->ipool_req_list, req_p);
466 mutex_exit(&pool_p->ipool_lock);
467 kmem_free(req_p, sizeof (ddi_irm_req_t));
468 return (DDI_EAGAIN);
469 }
470
471 /* Unlock the pool */
472 mutex_exit(&pool_p->ipool_lock);
473
474 intr_p->devi_irm_req_p = req_p;
475 return (DDI_SUCCESS);
476 }
477
478 /*
479 * i_ddi_irm_modify()
480 *
481 * Modify an existing request in an interrupt pool, and balance the pool.
482 */
483 int
i_ddi_irm_modify(dev_info_t * dip,int nreq)484 i_ddi_irm_modify(dev_info_t *dip, int nreq)
485 {
486 devinfo_intr_t *intr_p;
487 ddi_irm_req_t *req_p;
488 ddi_irm_pool_t *pool_p;
489 int type;
490 int retval = DDI_SUCCESS;
491
492 ASSERT(dip != NULL);
493 ASSERT(nreq > 0);
494
495 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_modify: dip %p nreq %d\n",
496 (void *)dip, nreq));
497
498 /* Validate parameters */
499 if ((dip == NULL) || (nreq < 1)) {
500 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_modify: invalid args\n"));
501 return (DDI_EINVAL);
502 }
503
504 /* Do nothing if not mapped to an IRM pool */
505 if (((intr_p = DEVI(dip)->devi_intr_p) == NULL) ||
506 ((req_p = intr_p->devi_irm_req_p) == NULL))
507 return (DDI_SUCCESS);
508
509 /* Do nothing if new size is the same */
510 if (nreq == req_p->ireq_nreq)
511 return (DDI_SUCCESS);
512
513 /* Do not allow MSI requests to be resized */
514 if ((type = req_p->ireq_type) == DDI_INTR_TYPE_MSI) {
515 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_modify: invalid type\n"));
516 return (DDI_ENOTSUP);
517 }
518
519 /* Select the pool */
520 if ((pool_p = req_p->ireq_pool_p) == NULL) {
521 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_modify: missing pool\n"));
522 return (DDI_FAILURE);
523 }
524
525 /* Validate request size is not too large */
526 if (nreq > i_ddi_intr_get_limit(dip, type, pool_p)) {
527 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_modify: invalid args\n"));
528 return (DDI_EINVAL);
529 }
530
531 /* Lock the pool */
532 mutex_enter(&pool_p->ipool_lock);
533
534 /*
535 * Process the modification.
536 *
537 * - To increase a non-IRM request, call the implementation in
538 * i_ddi_irm_modify_increase().
539 *
540 * - To decrease a non-IRM request, directly update the pool and
541 * request, then queue the pool for later rebalancing.
542 *
543 * - To modify an IRM request, always queue the pool for later
544 * rebalancing. IRM consumers rely upon callbacks for changes.
545 */
546 if ((nreq > req_p->ireq_nreq) &&
547 (i_ddi_irm_supported(dip, type) != DDI_SUCCESS)) {
548
549 retval = i_ddi_irm_modify_increase(req_p, nreq);
550
551 } else {
552
553 /* Update pool and request */
554 pool_p->ipool_reqno -= req_p->ireq_nreq;
555 pool_p->ipool_reqno += nreq;
556 if (i_ddi_irm_supported(dip, type) != DDI_SUCCESS) {
557 pool_p->ipool_minno -= req_p->ireq_navail;
558 pool_p->ipool_resno -= req_p->ireq_navail;
559 pool_p->ipool_minno += nreq;
560 pool_p->ipool_resno += nreq;
561 req_p->ireq_navail = nreq;
562 }
563 req_p->ireq_nreq = nreq;
564
565 /* Re-sort request into the pool */
566 list_remove(&pool_p->ipool_req_list, req_p);
567 i_ddi_irm_insertion_sort(&pool_p->ipool_req_list, req_p);
568
569 /* Queue pool for asynchronous rebalance */
570 i_ddi_irm_enqueue(pool_p, B_FALSE);
571 }
572
573 /* Unlock the pool */
574 mutex_exit(&pool_p->ipool_lock);
575
576 return (retval);
577 }
578
579 /*
580 * i_ddi_irm_modify_increase()
581 *
582 * Increase a non-IRM request. The additional interrupts are
583 * directly taken from the pool when possible. Otherwise, an
584 * immediate, synchronous rebalance is performed. A temporary
585 * proxy request is used for any rebalance operation to ensure
586 * the request is not reduced below its current allocation.
587 *
588 * NOTE: pool must already be locked.
589 */
590 static int
i_ddi_irm_modify_increase(ddi_irm_req_t * req_p,int nreq)591 i_ddi_irm_modify_increase(ddi_irm_req_t *req_p, int nreq)
592 {
593 dev_info_t *dip = req_p->ireq_dip;
594 ddi_irm_pool_t *pool_p = req_p->ireq_pool_p;
595 ddi_irm_req_t new_req;
596 int count, delta;
597
598 ASSERT(MUTEX_HELD(&pool_p->ipool_lock));
599
600 /* Compute number of additional vectors */
601 count = nreq - req_p->ireq_nreq;
602
603 /* Check for minimal fit */
604 if ((pool_p->ipool_minno + count) > pool_p->ipool_totsz) {
605 cmn_err(CE_WARN, "%s%d: interrupt pool too full.\n",
606 ddi_driver_name(dip), ddi_get_instance(dip));
607 return (DDI_EAGAIN);
608 }
609
610 /* Update the pool */
611 pool_p->ipool_reqno += count;
612 pool_p->ipool_minno += count;
613
614 /* Attempt direct implementation */
615 if ((pool_p->ipool_resno + count) <= pool_p->ipool_totsz) {
616 req_p->ireq_nreq += count;
617 req_p->ireq_navail += count;
618 pool_p->ipool_resno += count;
619 return (DDI_SUCCESS);
620 }
621
622 /* Rebalance required: fail if pool is not active */
623 if ((pool_p->ipool_flags & DDI_IRM_FLAG_ACTIVE) == 0) {
624 pool_p->ipool_reqno -= count;
625 pool_p->ipool_minno -= count;
626 return (DDI_EAGAIN);
627 }
628
629 /* Insert temporary proxy request */
630 bzero(&new_req, sizeof (ddi_irm_req_t));
631 new_req.ireq_dip = dip;
632 new_req.ireq_nreq = count;
633 new_req.ireq_pool_p = pool_p;
634 new_req.ireq_type = req_p->ireq_type;
635 new_req.ireq_flags = DDI_IRM_FLAG_NEW;
636 i_ddi_irm_insertion_sort(&pool_p->ipool_req_list, &new_req);
637
638 /* Synchronously rebalance */
639 i_ddi_irm_enqueue(pool_p, B_TRUE);
640
641 /* Remove proxy request, and merge into original request */
642 req_p->ireq_nreq += count;
643 if ((delta = (count - new_req.ireq_navail)) > 0) {
644 req_p->ireq_nreq -= delta;
645 pool_p->ipool_reqno -= delta;
646 pool_p->ipool_minno -= delta;
647 }
648 req_p->ireq_navail += new_req.ireq_navail;
649 list_remove(&pool_p->ipool_req_list, req_p);
650 list_remove(&pool_p->ipool_req_list, &new_req);
651 i_ddi_irm_insertion_sort(&pool_p->ipool_req_list, req_p);
652
653 return (DDI_SUCCESS);
654 }
655
656 /*
657 * i_ddi_irm_remove()
658 *
659 * Remove a request from an interrupt pool, and balance the pool.
660 */
661 int
i_ddi_irm_remove(dev_info_t * dip)662 i_ddi_irm_remove(dev_info_t *dip)
663 {
664 devinfo_intr_t *intr_p;
665 ddi_irm_pool_t *pool_p;
666 ddi_irm_req_t *req_p;
667 uint_t nmin;
668
669 ASSERT(dip != NULL);
670
671 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_remove: dip %p\n", (void *)dip));
672
673 /* Validate parameters */
674 if (dip == NULL) {
675 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_remove: invalid args\n"));
676 return (DDI_EINVAL);
677 }
678
679 /* Check if the device has a request */
680 if (!(intr_p = DEVI(dip)->devi_intr_p) ||
681 !(req_p = intr_p->devi_irm_req_p)) {
682 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_modify: not found\n"));
683 return (DDI_EINVAL);
684 }
685
686 /* Lock the pool */
687 pool_p = req_p->ireq_pool_p;
688 mutex_enter(&pool_p->ipool_lock);
689
690 /* Remove request */
691 nmin = DDI_IRM_IS_REDUCIBLE(req_p) ? 1 : req_p->ireq_nreq;
692 pool_p->ipool_minno -= nmin;
693 pool_p->ipool_reqno -= req_p->ireq_nreq;
694 pool_p->ipool_resno -= req_p->ireq_navail;
695 list_remove(&pool_p->ipool_req_list, req_p);
696
697 /* Queue pool to be rebalanced */
698 i_ddi_irm_enqueue(pool_p, B_FALSE);
699
700 /* Unlock the pool */
701 mutex_exit(&pool_p->ipool_lock);
702
703 /* Destroy the request */
704 intr_p->devi_irm_req_p = NULL;
705 kmem_free(req_p, sizeof (ddi_irm_req_t));
706
707 return (DDI_SUCCESS);
708 }
709
710 /*
711 * i_ddi_irm_set_cb()
712 *
713 * Change the callback flag for a request, in response to
714 * a change in its callback registration. Then rebalance
715 * the interrupt pool.
716 *
717 * NOTE: the request is not locked because the navail value
718 * is not directly affected. The balancing thread may
719 * modify the navail value in the background after it
720 * locks the request itself.
721 */
722 void
i_ddi_irm_set_cb(dev_info_t * dip,boolean_t has_cb_flag)723 i_ddi_irm_set_cb(dev_info_t *dip, boolean_t has_cb_flag)
724 {
725 devinfo_intr_t *intr_p;
726 ddi_irm_pool_t *pool_p;
727 ddi_irm_req_t *req_p;
728 uint_t nreq;
729
730 ASSERT(dip != NULL);
731
732 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_set_cb: dip %p has_cb_flag %d\n",
733 (void *)dip, (int)has_cb_flag));
734
735 /* Validate parameters */
736 if (dip == NULL)
737 return;
738
739 /* Check for association with interrupt pool */
740 if (!(intr_p = DEVI(dip)->devi_intr_p) ||
741 !(req_p = intr_p->devi_irm_req_p)) {
742 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_set_cb: not in pool\n"));
743 return;
744 }
745
746 /* Lock the pool */
747 pool_p = req_p->ireq_pool_p;
748 mutex_enter(&pool_p->ipool_lock);
749
750 /*
751 * Update the request and the pool
752 */
753 if (has_cb_flag) {
754
755 /* Update pool statistics */
756 if (req_p->ireq_type == DDI_INTR_TYPE_MSIX)
757 pool_p->ipool_minno -= (req_p->ireq_nreq - 1);
758
759 /* Update request */
760 req_p->ireq_flags |= DDI_IRM_FLAG_CALLBACK;
761
762 /* Rebalance in background */
763 i_ddi_irm_enqueue(pool_p, B_FALSE);
764
765 } else {
766
767 /* Determine new request size */
768 nreq = MIN(req_p->ireq_nreq, pool_p->ipool_defsz);
769
770 #if defined(__x86)
771 /* Use the default static limit for non-IRM drivers */
772 if (req_p->ireq_type == DDI_INTR_TYPE_MSIX)
773 nreq = MIN(nreq, ddi_msix_alloc_limit);
774 #endif
775
776 /* Update pool statistics */
777 pool_p->ipool_reqno -= req_p->ireq_nreq;
778 pool_p->ipool_reqno += nreq;
779 if (req_p->ireq_type == DDI_INTR_TYPE_MSIX) {
780 pool_p->ipool_minno -= 1;
781 pool_p->ipool_minno += nreq;
782 } else {
783 pool_p->ipool_minno -= req_p->ireq_nreq;
784 pool_p->ipool_minno += nreq;
785 }
786
787 /* Update request size, and re-sort in pool */
788 req_p->ireq_nreq = nreq;
789 list_remove(&pool_p->ipool_req_list, req_p);
790 i_ddi_irm_insertion_sort(&pool_p->ipool_req_list, req_p);
791
792 /* Rebalance synchronously, before losing callback */
793 i_ddi_irm_enqueue(pool_p, B_TRUE);
794
795 /* Remove callback flag */
796 req_p->ireq_flags &= ~(DDI_IRM_FLAG_CALLBACK);
797 }
798
799 /* Unlock the pool */
800 mutex_exit(&pool_p->ipool_lock);
801 }
802
803 /*
804 * i_ddi_irm_supported()
805 *
806 * Query if IRM is supported by a driver using a specific interrupt type.
807 * Notice that IRM is limited to MSI-X users with registered callbacks.
808 */
809 int
i_ddi_irm_supported(dev_info_t * dip,int type)810 i_ddi_irm_supported(dev_info_t *dip, int type)
811 {
812 ddi_cb_t *cb_p = DEVI(dip)->devi_cb_p;
813
814 return ((DDI_IRM_HAS_CB(cb_p) && (type == DDI_INTR_TYPE_MSIX)) ?
815 DDI_SUCCESS : DDI_ENOTSUP);
816 }
817
818 /*
819 * Interrupt Pool Balancing
820 */
821
822 /*
823 * irm_balance_thread()
824 *
825 * One instance of this thread operates per each defined IRM pool.
826 * It does the initial activation of the pool, as well as balancing
827 * any requests that were queued up before the pool was active.
828 * Once active, it waits forever to service balance operations.
829 */
830 static void
irm_balance_thread(ddi_irm_pool_t * pool_p)831 irm_balance_thread(ddi_irm_pool_t *pool_p)
832 {
833 clock_t interval;
834
835 DDI_INTR_IRMDBG((CE_CONT, "irm_balance_thread: pool_p %p\n",
836 (void *)pool_p));
837
838 /* Lock the pool */
839 mutex_enter(&pool_p->ipool_lock);
840
841 /* Perform initial balance if required */
842 if (pool_p->ipool_reqno > pool_p->ipool_resno)
843 i_ddi_irm_balance(pool_p);
844
845 /* Activate the pool */
846 pool_p->ipool_flags |= DDI_IRM_FLAG_ACTIVE;
847
848 /*
849 * Main loop.
850 * Iterate once first before wait on signal, in case there is signal
851 * sent before this thread being created
852 */
853 for (;;) {
854
855 /* Compute the delay interval */
856 interval = drv_usectohz(irm_balance_delay * 1000000);
857
858 /* Wait one interval, or until there are waiters */
859 if ((interval > 0) &&
860 !(pool_p->ipool_flags & DDI_IRM_FLAG_WAITERS) &&
861 !(pool_p->ipool_flags & DDI_IRM_FLAG_EXIT)) {
862 (void) cv_reltimedwait(&pool_p->ipool_cv,
863 &pool_p->ipool_lock, interval, TR_CLOCK_TICK);
864 }
865
866 /* Check if awakened to exit */
867 if (pool_p->ipool_flags & DDI_IRM_FLAG_EXIT) {
868 DDI_INTR_IRMDBG((CE_CONT,
869 "irm_balance_thread: exiting...\n"));
870 mutex_exit(&pool_p->ipool_lock);
871 thread_exit();
872 }
873
874 /* Balance the pool */
875 i_ddi_irm_balance(pool_p);
876
877 /* Notify waiters */
878 if (pool_p->ipool_flags & DDI_IRM_FLAG_WAITERS) {
879 cv_broadcast(&pool_p->ipool_cv);
880 pool_p->ipool_flags &= ~(DDI_IRM_FLAG_WAITERS);
881 }
882
883 /* Clear QUEUED condition */
884 pool_p->ipool_flags &= ~(DDI_IRM_FLAG_QUEUED);
885
886 /* Sleep until queued */
887 cv_wait(&pool_p->ipool_cv, &pool_p->ipool_lock);
888
889 DDI_INTR_IRMDBG((CE_CONT, "irm_balance_thread: signaled.\n"));
890 }
891 }
892
893 /*
894 * i_ddi_irm_balance()
895 *
896 * Balance a pool. The general algorithm is to first reset all
897 * requests to their maximum size, use reduction algorithms to
898 * solve any imbalance, and then notify affected drivers.
899 */
900 static void
i_ddi_irm_balance(ddi_irm_pool_t * pool_p)901 i_ddi_irm_balance(ddi_irm_pool_t *pool_p)
902 {
903 ddi_irm_req_t *req_p;
904
905 #ifdef DEBUG
906 uint_t debug_totsz = 0;
907 int debug_policy = 0;
908 #endif /* DEBUG */
909
910 ASSERT(pool_p != NULL);
911 ASSERT(MUTEX_HELD(&pool_p->ipool_lock));
912
913 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_balance: pool_p %p\n",
914 (void *)pool_p));
915
916 #ifndef DEBUG
917 if ((pool_p->ipool_reqno == pool_p->ipool_resno)) {
918 #else
919 if ((pool_p->ipool_reqno == pool_p->ipool_resno) && !irm_debug_size) {
920 #endif /* DEBUG */
921 DDI_INTR_IRMDBG((CE_CONT,
922 "i_ddi_irm_balance: pool already balanced\n"));
923 return;
924 }
925
926 #ifdef DEBUG /* Adjust size and policy settings */
927 if (irm_debug_size > pool_p->ipool_minno) {
928 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_balance: debug size %d\n",
929 irm_debug_size));
930 debug_totsz = pool_p->ipool_totsz;
931 pool_p->ipool_totsz = irm_debug_size;
932 }
933 if (DDI_IRM_POLICY_VALID(irm_debug_policy)) {
934 DDI_INTR_IRMDBG((CE_CONT,
935 "i_ddi_irm_balance: debug policy %d\n", irm_debug_policy));
936 debug_policy = pool_p->ipool_policy;
937 pool_p->ipool_policy = irm_debug_policy;
938 }
939 #endif /* DEBUG */
940
941 /* Lock the availability lock */
942 mutex_enter(&pool_p->ipool_navail_lock);
943
944 /*
945 * Put all of the reducible requests into a scratch list.
946 * Reset each one of them to their maximum availability.
947 */
948 for (req_p = list_head(&pool_p->ipool_req_list); req_p;
949 req_p = list_next(&pool_p->ipool_req_list, req_p)) {
950 if (DDI_IRM_IS_REDUCIBLE(req_p)) {
951 pool_p->ipool_resno -= req_p->ireq_navail;
952 req_p->ireq_scratch = req_p->ireq_navail;
953 req_p->ireq_navail = req_p->ireq_nreq;
954 pool_p->ipool_resno += req_p->ireq_navail;
955 list_insert_tail(&pool_p->ipool_scratch_list, req_p);
956 }
957 }
958
959 /* Balance the requests */
960 i_ddi_irm_reduce(pool_p);
961
962 /* Unlock the availability lock */
963 mutex_exit(&pool_p->ipool_navail_lock);
964
965 /*
966 * Process REMOVE notifications.
967 *
968 * If a driver fails to release interrupts: exclude it from
969 * further processing, correct the resulting imbalance, and
970 * start over again at the head of the scratch list.
971 */
972 req_p = list_head(&pool_p->ipool_scratch_list);
973 while (req_p) {
974 if ((req_p->ireq_navail < req_p->ireq_scratch) &&
975 (i_ddi_irm_notify(pool_p, req_p) != DDI_SUCCESS)) {
976 list_remove(&pool_p->ipool_scratch_list, req_p);
977 mutex_enter(&pool_p->ipool_navail_lock);
978 i_ddi_irm_reduce(pool_p);
979 mutex_exit(&pool_p->ipool_navail_lock);
980 req_p = list_head(&pool_p->ipool_scratch_list);
981 } else {
982 req_p = list_next(&pool_p->ipool_scratch_list, req_p);
983 }
984 }
985
986 /*
987 * Process ADD notifications.
988 *
989 * This is the last use of the scratch list, so empty it.
990 */
991 while (req_p = list_remove_head(&pool_p->ipool_scratch_list)) {
992 if (req_p->ireq_navail > req_p->ireq_scratch) {
993 (void) i_ddi_irm_notify(pool_p, req_p);
994 }
995 }
996
997 #ifdef DEBUG /* Restore size and policy settings */
998 if (debug_totsz != 0)
999 pool_p->ipool_totsz = debug_totsz;
1000 if (debug_policy != 0)
1001 pool_p->ipool_policy = debug_policy;
1002 #endif /* DEBUG */
1003 }
1004
1005 /*
1006 * i_ddi_irm_reduce()
1007 *
1008 * Use reduction algorithms to correct an imbalance in a pool.
1009 */
1010 static void
1011 i_ddi_irm_reduce(ddi_irm_pool_t *pool_p)
1012 {
1013 int imbalance;
1014
1015 ASSERT(pool_p != NULL);
1016 ASSERT(MUTEX_HELD(&pool_p->ipool_lock));
1017 ASSERT(DDI_IRM_POLICY_VALID(pool_p->ipool_policy));
1018
1019 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_reduce: pool_p %p\n",
1020 (void *)pool_p));
1021
1022 /* Compute the imbalance. Do nothing if already balanced. */
1023 if ((imbalance = pool_p->ipool_resno - pool_p->ipool_totsz) <= 0)
1024 return;
1025
1026 /*
1027 * Try policy based reduction first. If it failed, then
1028 * possibly reduce new requests as a last resort.
1029 */
1030 if (i_ddi_irm_reduce_by_policy(pool_p, imbalance, pool_p->ipool_policy)
1031 != DDI_SUCCESS) {
1032
1033 DDI_INTR_IRMDBG((CE_CONT,
1034 "i_ddi_irm_reduce: policy reductions failed.\n"));
1035
1036 /* Compute remaining imbalance */
1037 imbalance = pool_p->ipool_resno - pool_p->ipool_totsz;
1038
1039 ASSERT(imbalance > 0);
1040
1041 i_ddi_irm_reduce_new(pool_p, imbalance);
1042 }
1043 }
1044
1045 /*
1046 * i_ddi_irm_enqueue()
1047 *
1048 * Queue a pool to be balanced. Signals the balancing thread to wake
1049 * up and process the pool. If 'wait_flag' is true, then the current
1050 * thread becomes a waiter and blocks until the balance is completed.
1051 */
1052 static void
1053 i_ddi_irm_enqueue(ddi_irm_pool_t *pool_p, boolean_t wait_flag)
1054 {
1055 ASSERT(pool_p != NULL);
1056 ASSERT(MUTEX_HELD(&pool_p->ipool_lock));
1057
1058 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_enqueue: pool_p %p wait_flag %d\n",
1059 (void *)pool_p, (int)wait_flag));
1060
1061 /* Do nothing if pool is already balanced */
1062 #ifndef DEBUG
1063 if ((pool_p->ipool_reqno == pool_p->ipool_resno)) {
1064 #else
1065 if ((pool_p->ipool_reqno == pool_p->ipool_resno) && !irm_debug_size) {
1066 #endif /* DEBUG */
1067 DDI_INTR_IRMDBG((CE_CONT,
1068 "i_ddi_irm_enqueue: pool already balanced\n"));
1069 return;
1070 }
1071
1072 /* Avoid deadlocks when IRM is not active */
1073 if (!irm_active && wait_flag) {
1074 DDI_INTR_IRMDBG((CE_CONT,
1075 "i_ddi_irm_enqueue: pool not active.\n"));
1076 return;
1077 }
1078
1079 if (wait_flag)
1080 pool_p->ipool_flags |= DDI_IRM_FLAG_WAITERS;
1081
1082 if (wait_flag || !(pool_p->ipool_flags & DDI_IRM_FLAG_QUEUED)) {
1083 pool_p->ipool_flags |= DDI_IRM_FLAG_QUEUED;
1084 cv_signal(&pool_p->ipool_cv);
1085 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_enqueue: pool queued.\n"));
1086 }
1087
1088 if (wait_flag) {
1089 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_enqueue: waiting...\n"));
1090 cv_wait(&pool_p->ipool_cv, &pool_p->ipool_lock);
1091 }
1092 }
1093
1094 /*
1095 * i_ddi_irm_reduce_by_policy()
1096 *
1097 * Reduces requests based on reduction policies.
1098 *
1099 * For the DDI_IRM_POLICY_LARGE reduction policy, the algorithm
1100 * generally reduces larger requests first, before advancing
1101 * to smaller requests.
1102 * For the DDI_IRM_POLICY_EVEN reduction policy, the algorithm
1103 * reduces requests evenly, without giving a specific preference
1104 * to smaller or larger requests. Each iteration reduces all
1105 * reducible requests by the same amount until the imbalance is
1106 * corrected.
1107 *
1108 * The scratch list is initially sorted in descending order by current
1109 * navail values, which are maximized prior to reduction. This sorted
1110 * order is preserved. It avoids reducing requests below the threshold
1111 * of the interrupt pool's default allocation size.
1112 *
1113 * Optimizations in this algorithm include trying to reduce multiple
1114 * requests together. And the algorithm attempts to reduce in larger
1115 * increments when possible to minimize the total number of iterations.
1116 */
1117 static int
1118 i_ddi_irm_reduce_by_policy(ddi_irm_pool_t *pool_p, int imbalance, int policy)
1119 {
1120 ASSERT(pool_p != NULL);
1121 ASSERT(imbalance > 0);
1122 ASSERT(MUTEX_HELD(&pool_p->ipool_lock));
1123
1124 while (imbalance > 0) {
1125 list_t *slist_p = &pool_p->ipool_scratch_list;
1126 ddi_irm_req_t *req_p = list_head(slist_p), *last_p;
1127 uint_t nreduce = 0, nremain = 0, stop_navail;
1128 uint_t pool_defsz = pool_p->ipool_defsz;
1129 uint_t reduction, max_redu;
1130
1131 /* Fail if none are reducible */
1132 if (!req_p || req_p->ireq_navail <= pool_defsz) {
1133 DDI_INTR_IRMDBG((CE_CONT,
1134 "i_ddi_irm_reduce_by_policy: Failure. "
1135 "All requests have downsized to low limit.\n"));
1136 return (DDI_FAILURE);
1137 }
1138
1139 /* Count reducible requests */
1140 stop_navail = (policy == DDI_IRM_POLICY_LARGE) ?
1141 req_p->ireq_navail - 1 : pool_defsz;
1142 for (; req_p; req_p = list_next(slist_p, req_p)) {
1143 if (req_p->ireq_navail <= stop_navail)
1144 break;
1145 nreduce++;
1146 }
1147
1148 /* Compute reduction */
1149 last_p = req_p ? list_prev(slist_p, req_p) : list_tail(slist_p);
1150 if ((policy == DDI_IRM_POLICY_LARGE) && req_p &&
1151 req_p->ireq_navail > pool_defsz)
1152 reduction = last_p->ireq_navail - req_p->ireq_navail;
1153 else
1154 reduction = last_p->ireq_navail - pool_defsz;
1155
1156 if ((max_redu = reduction * nreduce) > imbalance) {
1157 reduction = imbalance / nreduce;
1158 nremain = imbalance % nreduce;
1159 pool_p->ipool_resno -= imbalance;
1160 imbalance = 0;
1161 } else {
1162 pool_p->ipool_resno -= max_redu;
1163 imbalance -= max_redu;
1164 }
1165
1166 /* Reduce */
1167 for (req_p = list_head(slist_p); (reduction != 0) && nreduce--;
1168 req_p = list_next(slist_p, req_p)) {
1169 req_p->ireq_navail -= reduction;
1170 }
1171
1172 for (req_p = last_p; nremain--;
1173 req_p = list_prev(slist_p, req_p)) {
1174 req_p->ireq_navail--;
1175 }
1176 }
1177
1178 return (DDI_SUCCESS);
1179 }
1180
1181 /*
1182 * i_ddi_irm_reduce_new()
1183 *
1184 * Reduces new requests. This is only used as a last resort
1185 * after another reduction algorithm failed.
1186 *
1187 * NOTE: The pool locking in i_ddi_irm_insert() ensures
1188 * there can be only one new request at a time in a pool.
1189 */
1190 static void
1191 i_ddi_irm_reduce_new(ddi_irm_pool_t *pool_p, int imbalance)
1192 {
1193 ddi_irm_req_t *req_p;
1194
1195 ASSERT(pool_p != NULL);
1196 ASSERT(imbalance > 0);
1197 ASSERT(MUTEX_HELD(&pool_p->ipool_lock));
1198
1199 DDI_INTR_IRMDBG((CE_CONT,
1200 "i_ddi_irm_reduce_new: pool_p %p imbalance %d\n",
1201 (void *)pool_p, imbalance));
1202
1203 for (req_p = list_head(&pool_p->ipool_scratch_list); req_p;
1204 req_p = list_next(&pool_p->ipool_scratch_list, req_p)) {
1205 if (req_p->ireq_flags & DDI_IRM_FLAG_NEW) {
1206 ASSERT(req_p->ireq_navail >= imbalance);
1207 req_p->ireq_navail -= imbalance;
1208 pool_p->ipool_resno -= imbalance;
1209 return;
1210 }
1211 }
1212
1213 /* should never go here */
1214 ASSERT(B_FALSE);
1215 }
1216
1217 /*
1218 * Miscellaneous Helper Functions
1219 */
1220
1221 /*
1222 * i_ddi_intr_get_pool()
1223 *
1224 * Get an IRM pool that supplies interrupts of a specified type.
1225 * Invokes a DDI_INTROP_GETPOOL to the bus nexus driver. Fails
1226 * if no pool exists.
1227 */
1228 ddi_irm_pool_t *
1229 i_ddi_intr_get_pool(dev_info_t *dip, int type)
1230 {
1231 devinfo_intr_t *intr_p;
1232 ddi_irm_pool_t *pool_p;
1233 ddi_irm_req_t *req_p;
1234 ddi_intr_handle_impl_t hdl;
1235
1236 ASSERT(dip != NULL);
1237 ASSERT(DDI_INTR_TYPE_FLAG_VALID(type));
1238
1239 if (((intr_p = DEVI(dip)->devi_intr_p) != NULL) &&
1240 ((req_p = intr_p->devi_irm_req_p) != NULL) &&
1241 ((pool_p = req_p->ireq_pool_p) != NULL) &&
1242 (pool_p->ipool_types & type)) {
1243 return (pool_p);
1244 }
1245
1246 bzero(&hdl, sizeof (ddi_intr_handle_impl_t));
1247 hdl.ih_dip = dip;
1248 hdl.ih_type = type;
1249
1250 if (i_ddi_intr_ops(dip, dip, DDI_INTROP_GETPOOL,
1251 &hdl, (void *)&pool_p) == DDI_SUCCESS)
1252 return (pool_p);
1253
1254 return (NULL);
1255 }
1256
1257 /*
1258 * i_ddi_irm_insertion_sort()
1259 *
1260 * Use the insertion sort method to insert a request into a list.
1261 * The list is sorted in descending order by request size.
1262 */
1263 static void
1264 i_ddi_irm_insertion_sort(list_t *req_list, ddi_irm_req_t *req_p)
1265 {
1266 ddi_irm_req_t *next_p;
1267
1268 next_p = list_head(req_list);
1269
1270 while (next_p && (next_p->ireq_nreq > req_p->ireq_nreq))
1271 next_p = list_next(req_list, next_p);
1272
1273 list_insert_before(req_list, next_p, req_p);
1274 }
1275
1276 /*
1277 * i_ddi_irm_notify()
1278 *
1279 * Notify a driver of changes to its interrupt request using the
1280 * generic callback mechanism. Checks for errors in processing.
1281 */
1282 static int
1283 i_ddi_irm_notify(ddi_irm_pool_t *pool_p, ddi_irm_req_t *req_p)
1284 {
1285 ddi_cb_action_t action;
1286 ddi_cb_t *cb_p;
1287 uint_t nintrs;
1288 int ret, count;
1289
1290 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_notify: pool_p %p req_p %p\n",
1291 (void *)pool_p, (void *)req_p));
1292
1293 /* Do not notify new or unchanged requests */
1294 if ((req_p->ireq_navail == req_p->ireq_scratch) ||
1295 (req_p->ireq_flags & DDI_IRM_FLAG_NEW))
1296 return (DDI_SUCCESS);
1297
1298 /* Determine action and count */
1299 if (req_p->ireq_navail > req_p->ireq_scratch) {
1300 action = DDI_CB_INTR_ADD;
1301 count = req_p->ireq_navail - req_p->ireq_scratch;
1302 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_notify: adding %d\n",
1303 count));
1304 } else {
1305 action = DDI_CB_INTR_REMOVE;
1306 count = req_p->ireq_scratch - req_p->ireq_navail;
1307 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_notify: removing %d\n",
1308 count));
1309 }
1310
1311 /* Lookup driver callback */
1312 if ((cb_p = DEVI(req_p->ireq_dip)->devi_cb_p) == NULL) {
1313 DDI_INTR_IRMDBG((CE_WARN, "i_ddi_irm_notify: no callback!\n"));
1314 return (DDI_FAILURE);
1315 }
1316
1317 /* Do callback */
1318 ret = cb_p->cb_func(req_p->ireq_dip, action, (void *)(uintptr_t)count,
1319 cb_p->cb_arg1, cb_p->cb_arg2);
1320
1321 /* Log callback errors */
1322 if (ret != DDI_SUCCESS) {
1323 cmn_err(CE_WARN, "%s%d: failed callback (action=%d, ret=%d)\n",
1324 ddi_driver_name(req_p->ireq_dip),
1325 ddi_get_instance(req_p->ireq_dip), (int)action, ret);
1326 }
1327
1328 /* Check if the driver exceeds its availability */
1329 nintrs = i_ddi_intr_get_current_nintrs(req_p->ireq_dip);
1330 if (nintrs > req_p->ireq_navail) {
1331 cmn_err(CE_WARN, "%s%d: failed to release interrupts "
1332 "(nintrs=%d, navail=%d).\n",
1333 ddi_driver_name(req_p->ireq_dip),
1334 ddi_get_instance(req_p->ireq_dip), nintrs,
1335 req_p->ireq_navail);
1336 pool_p->ipool_resno += (nintrs - req_p->ireq_navail);
1337 req_p->ireq_navail = nintrs;
1338 return (DDI_FAILURE);
1339 }
1340
1341 /* Update request */
1342 req_p->ireq_scratch = req_p->ireq_navail;
1343
1344 return (DDI_SUCCESS);
1345 }
1346
1347 /*
1348 * i_ddi_irm_debug_balance()
1349 *
1350 * A debug/test only routine to force the immediate,
1351 * synchronous rebalancing of an interrupt pool.
1352 */
1353 #ifdef DEBUG
1354 void
1355 i_ddi_irm_debug_balance(dev_info_t *dip, boolean_t wait_flag)
1356 {
1357 ddi_irm_pool_t *pool_p;
1358 int type;
1359
1360 DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_debug_balance: dip %p wait %d\n",
1361 (void *)dip, (int)wait_flag));
1362
1363 if (((type = i_ddi_intr_get_current_type(dip)) != 0) &&
1364 ((pool_p = i_ddi_intr_get_pool(dip, type)) != NULL)) {
1365 mutex_enter(&pool_p->ipool_lock);
1366 i_ddi_irm_enqueue(pool_p, wait_flag);
1367 mutex_exit(&pool_p->ipool_lock);
1368 }
1369 }
1370 #endif
1371