#include <sys/note.h>
#include <sys/sysmacros.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kmem.h>
#include <sys/cmn_err.h>
#include <sys/debug.h>
#include <sys/ddi.h>
#include <sys/sunndi.h>
#include <sys/ndi_impldefs.h>
#if defined(__x86)
extern uint_t ddi_msix_alloc_limit;
#endif
#define DDI_IRM_BALANCE_DELAY (60)
#define DDI_IRM_HAS_CB(c) ((c) && (c->cb_flags & DDI_CB_FLAG_INTR))
#define DDI_IRM_IS_REDUCIBLE(r) (((r->ireq_flags & DDI_IRM_FLAG_CALLBACK) && \
(r->ireq_type == DDI_INTR_TYPE_MSIX)) || \
(r->ireq_flags & DDI_IRM_FLAG_NEW))
extern pri_t minclsyspri;
int irm_enable = 1;
boolean_t irm_active = B_FALSE;
int irm_default_policy = DDI_IRM_POLICY_LARGE;
uint_t irm_balance_delay = DDI_IRM_BALANCE_DELAY;
kmutex_t irm_pools_lock;
list_t irm_pools_list;
#ifdef DEBUG
int irm_debug_policy = 0;
uint_t irm_debug_size = 0;
#endif
static void irm_balance_thread(ddi_irm_pool_t *);
static void i_ddi_irm_balance(ddi_irm_pool_t *);
static void i_ddi_irm_enqueue(ddi_irm_pool_t *, boolean_t);
static void i_ddi_irm_reduce(ddi_irm_pool_t *pool);
static int i_ddi_irm_reduce_by_policy(ddi_irm_pool_t *, int, int);
static void i_ddi_irm_reduce_new(ddi_irm_pool_t *, int);
static void i_ddi_irm_insertion_sort(list_t *, ddi_irm_req_t *);
static int i_ddi_irm_notify(ddi_irm_pool_t *, ddi_irm_req_t *);
static int i_ddi_irm_modify_increase(ddi_irm_req_t *, int);
void
irm_init(void)
{
if (!irm_enable)
return;
if (!DDI_IRM_POLICY_VALID(irm_default_policy))
irm_default_policy = DDI_IRM_POLICY_LARGE;
mutex_init(&irm_pools_lock, NULL, MUTEX_DRIVER, NULL);
list_create(&irm_pools_list, sizeof (ddi_irm_pool_t),
offsetof(ddi_irm_pool_t, ipool_link));
}
void
i_ddi_irm_poststartup(void)
{
ddi_irm_pool_t *pool_p;
if (!irm_enable)
return;
mutex_enter(&irm_pools_lock);
for (pool_p = list_head(&irm_pools_list); pool_p;
pool_p = list_next(&irm_pools_list, pool_p))
pool_p->ipool_thread = thread_create(NULL, 0,
irm_balance_thread, pool_p, 0, &p0, TS_RUN, minclsyspri);
irm_active = B_TRUE;
mutex_exit(&irm_pools_lock);
}
int
ndi_irm_create(dev_info_t *dip, ddi_irm_params_t *paramsp,
ddi_irm_pool_t **pool_retp)
{
ddi_irm_pool_t *pool_p;
ASSERT(dip != NULL);
ASSERT(paramsp != NULL);
ASSERT(pool_retp != NULL);
ASSERT(paramsp->iparams_total >= 1);
ASSERT(paramsp->iparams_types != 0);
DDI_INTR_IRMDBG((CE_CONT, "ndi_irm_create: dip %p\n", (void *)dip));
if (!irm_enable)
return (NDI_FAILURE);
if ((dip == NULL) || (paramsp == NULL) || (pool_retp == NULL) ||
(paramsp->iparams_total < 1) || (paramsp->iparams_types == 0))
return (NDI_FAILURE);
pool_p = kmem_zalloc(sizeof (ddi_irm_pool_t), KM_SLEEP);
pool_p->ipool_owner = dip;
pool_p->ipool_policy = irm_default_policy;
pool_p->ipool_types = paramsp->iparams_types;
pool_p->ipool_totsz = paramsp->iparams_total;
pool_p->ipool_defsz = MIN(DDI_MAX_MSIX_ALLOC, MAX(DDI_MIN_MSIX_ALLOC,
paramsp->iparams_total / DDI_MSIX_ALLOC_DIVIDER));
list_create(&pool_p->ipool_req_list, sizeof (ddi_irm_req_t),
offsetof(ddi_irm_req_t, ireq_link));
list_create(&pool_p->ipool_scratch_list, sizeof (ddi_irm_req_t),
offsetof(ddi_irm_req_t, ireq_scratch_link));
cv_init(&pool_p->ipool_cv, NULL, CV_DRIVER, NULL);
mutex_init(&pool_p->ipool_lock, NULL, MUTEX_DRIVER, NULL);
mutex_init(&pool_p->ipool_navail_lock, NULL, MUTEX_DRIVER, NULL);
mutex_enter(&irm_pools_lock);
list_insert_tail(&irm_pools_list, pool_p);
mutex_exit(&irm_pools_lock);
if (irm_active)
pool_p->ipool_thread = thread_create(NULL, 0,
irm_balance_thread, pool_p, 0, &p0, TS_RUN, minclsyspri);
*pool_retp = pool_p;
return (NDI_SUCCESS);
}
int
ndi_irm_resize_pool(ddi_irm_pool_t *pool_p, uint_t new_size)
{
uint_t prev_size;
ASSERT(pool_p != NULL);
DDI_INTR_IRMDBG((CE_CONT, "ndi_irm_resize_pool: pool_p %p"
" current-size 0x%x new-size 0x%x\n",
(void *)pool_p, pool_p->ipool_totsz, new_size));
if (pool_p == NULL)
return (NDI_EINVAL);
if (!irm_enable)
return (NDI_FAILURE);
mutex_enter(&pool_p->ipool_lock);
if ((pool_p->ipool_totsz < new_size) ||
(pool_p->ipool_resno <= new_size)) {
pool_p->ipool_totsz = new_size;
pool_p->ipool_defsz = MIN(DDI_MAX_MSIX_ALLOC,
MAX(DDI_MIN_MSIX_ALLOC, new_size / DDI_MSIX_ALLOC_DIVIDER));
if (pool_p->ipool_reqno > pool_p->ipool_resno)
i_ddi_irm_enqueue(pool_p, B_FALSE);
mutex_exit(&pool_p->ipool_lock);
return (NDI_SUCCESS);
}
DDI_INTR_IRMDBG((CE_CONT, "ndi_irm_resize_pool: pool_p %p"
" needs a rebalance operation\n", (void *)pool_p));
prev_size = pool_p->ipool_totsz;
pool_p->ipool_totsz = new_size;
i_ddi_irm_enqueue(pool_p, B_TRUE);
if (pool_p->ipool_resno > new_size) {
pool_p->ipool_totsz = prev_size;
i_ddi_irm_enqueue(pool_p, B_FALSE);
mutex_exit(&pool_p->ipool_lock);
return (NDI_FAILURE);
} else {
pool_p->ipool_defsz = MIN(DDI_MAX_MSIX_ALLOC,
MAX(DDI_MIN_MSIX_ALLOC, new_size / DDI_MSIX_ALLOC_DIVIDER));
mutex_exit(&pool_p->ipool_lock);
DDI_INTR_IRMDBG((CE_CONT, "ndi_irm_resize_pool: pool_p %p"
" resized from %x to %x\n",
(void *)pool_p, prev_size, pool_p->ipool_totsz));
return (NDI_SUCCESS);
}
}
int
ndi_irm_destroy(ddi_irm_pool_t *pool_p)
{
ASSERT(pool_p != NULL);
ASSERT(pool_p->ipool_resno == 0);
DDI_INTR_IRMDBG((CE_CONT, "ndi_irm_destroy: pool_p %p\n",
(void *)pool_p));
if (pool_p == NULL)
return (NDI_FAILURE);
if (pool_p->ipool_resno != 0)
return (NDI_BUSY);
mutex_enter(&irm_pools_lock);
list_remove(&irm_pools_list, pool_p);
mutex_exit(&irm_pools_lock);
mutex_enter(&pool_p->ipool_lock);
if (pool_p->ipool_thread &&
(pool_p->ipool_flags & DDI_IRM_FLAG_ACTIVE)) {
pool_p->ipool_flags |= DDI_IRM_FLAG_EXIT;
cv_signal(&pool_p->ipool_cv);
mutex_exit(&pool_p->ipool_lock);
thread_join(pool_p->ipool_thread->t_did);
} else
mutex_exit(&pool_p->ipool_lock);
cv_destroy(&pool_p->ipool_cv);
mutex_destroy(&pool_p->ipool_lock);
mutex_destroy(&pool_p->ipool_navail_lock);
list_destroy(&pool_p->ipool_req_list);
list_destroy(&pool_p->ipool_scratch_list);
kmem_free(pool_p, sizeof (ddi_irm_pool_t));
return (NDI_SUCCESS);
}
int
i_ddi_irm_insert(dev_info_t *dip, int type, int count)
{
ddi_irm_req_t *req_p;
devinfo_intr_t *intr_p;
ddi_irm_pool_t *pool_p;
uint_t nreq, nmin, npartial;
boolean_t irm_flag = B_FALSE;
ASSERT(dip != NULL);
ASSERT(DDI_INTR_TYPE_FLAG_VALID(type));
ASSERT(count > 0);
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_insert: dip %p type %d count %d\n",
(void *)dip, type, count));
if ((dip == NULL) || (count < 1) || !DDI_INTR_TYPE_FLAG_VALID(type)) {
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_insert: invalid args\n"));
return (DDI_EINVAL);
}
if (((intr_p = DEVI(dip)->devi_intr_p) != NULL) &&
(intr_p->devi_irm_req_p != NULL))
return (DDI_SUCCESS);
if ((pool_p = i_ddi_intr_get_pool(dip, type)) == NULL) {
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_insert: not supported\n"));
return (DDI_ENOTSUP);
}
if (i_ddi_irm_supported(dip, type) == DDI_SUCCESS)
irm_flag = B_TRUE;
nreq = (irm_flag) ? count :
MIN(count, i_ddi_intr_get_limit(dip, type, pool_p));
nmin = (irm_flag) ? 1 : nreq;
npartial = MIN(nreq, pool_p->ipool_defsz);
req_p = kmem_zalloc(sizeof (ddi_irm_req_t), KM_SLEEP);
req_p->ireq_type = type;
req_p->ireq_dip = dip;
req_p->ireq_pool_p = pool_p;
req_p->ireq_nreq = nreq;
req_p->ireq_flags = DDI_IRM_FLAG_NEW;
if (irm_flag)
req_p->ireq_flags |= DDI_IRM_FLAG_CALLBACK;
mutex_enter(&pool_p->ipool_lock);
if ((pool_p->ipool_minno + nmin) > pool_p->ipool_totsz) {
cmn_err(CE_WARN, "%s%d: interrupt pool too full.\n",
ddi_driver_name(dip), ddi_get_instance(dip));
mutex_exit(&pool_p->ipool_lock);
kmem_free(req_p, sizeof (ddi_irm_req_t));
return (DDI_EAGAIN);
}
pool_p->ipool_reqno += nreq;
pool_p->ipool_minno += nmin;
i_ddi_irm_insertion_sort(&pool_p->ipool_req_list, req_p);
if ((!irm_flag || (pool_p->ipool_flags & DDI_IRM_FLAG_ACTIVE)) &&
((pool_p->ipool_resno + nreq) <= pool_p->ipool_totsz)) {
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_insert: "
"request completely fulfilled.\n"));
pool_p->ipool_resno += nreq;
req_p->ireq_navail = nreq;
req_p->ireq_flags &= ~(DDI_IRM_FLAG_NEW);
} else if (irm_flag &&
((pool_p->ipool_resno + npartial) <= pool_p->ipool_totsz)) {
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_insert: "
"request partially fulfilled.\n"));
pool_p->ipool_resno += npartial;
req_p->ireq_navail = npartial;
req_p->ireq_flags &= ~(DDI_IRM_FLAG_NEW);
i_ddi_irm_enqueue(pool_p, B_FALSE);
} else {
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_insert: "
"request needs immediate rebalance.\n"));
i_ddi_irm_enqueue(pool_p, B_TRUE);
req_p->ireq_flags &= ~(DDI_IRM_FLAG_NEW);
}
if (req_p->ireq_navail == 0) {
cmn_err(CE_WARN, "%s%d: interrupt pool too full.\n",
ddi_driver_name(dip), ddi_get_instance(dip));
pool_p->ipool_reqno -= nreq;
pool_p->ipool_minno -= nmin;
list_remove(&pool_p->ipool_req_list, req_p);
mutex_exit(&pool_p->ipool_lock);
kmem_free(req_p, sizeof (ddi_irm_req_t));
return (DDI_EAGAIN);
}
mutex_exit(&pool_p->ipool_lock);
intr_p->devi_irm_req_p = req_p;
return (DDI_SUCCESS);
}
int
i_ddi_irm_modify(dev_info_t *dip, int nreq)
{
devinfo_intr_t *intr_p;
ddi_irm_req_t *req_p;
ddi_irm_pool_t *pool_p;
int type;
int retval = DDI_SUCCESS;
ASSERT(dip != NULL);
ASSERT(nreq > 0);
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_modify: dip %p nreq %d\n",
(void *)dip, nreq));
if ((dip == NULL) || (nreq < 1)) {
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_modify: invalid args\n"));
return (DDI_EINVAL);
}
if (((intr_p = DEVI(dip)->devi_intr_p) == NULL) ||
((req_p = intr_p->devi_irm_req_p) == NULL))
return (DDI_SUCCESS);
if (nreq == req_p->ireq_nreq)
return (DDI_SUCCESS);
if ((type = req_p->ireq_type) == DDI_INTR_TYPE_MSI) {
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_modify: invalid type\n"));
return (DDI_ENOTSUP);
}
if ((pool_p = req_p->ireq_pool_p) == NULL) {
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_modify: missing pool\n"));
return (DDI_FAILURE);
}
if (nreq > i_ddi_intr_get_limit(dip, type, pool_p)) {
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_modify: invalid args\n"));
return (DDI_EINVAL);
}
mutex_enter(&pool_p->ipool_lock);
if ((nreq > req_p->ireq_nreq) &&
(i_ddi_irm_supported(dip, type) != DDI_SUCCESS)) {
retval = i_ddi_irm_modify_increase(req_p, nreq);
} else {
pool_p->ipool_reqno -= req_p->ireq_nreq;
pool_p->ipool_reqno += nreq;
if (i_ddi_irm_supported(dip, type) != DDI_SUCCESS) {
pool_p->ipool_minno -= req_p->ireq_navail;
pool_p->ipool_resno -= req_p->ireq_navail;
pool_p->ipool_minno += nreq;
pool_p->ipool_resno += nreq;
req_p->ireq_navail = nreq;
}
req_p->ireq_nreq = nreq;
list_remove(&pool_p->ipool_req_list, req_p);
i_ddi_irm_insertion_sort(&pool_p->ipool_req_list, req_p);
i_ddi_irm_enqueue(pool_p, B_FALSE);
}
mutex_exit(&pool_p->ipool_lock);
return (retval);
}
static int
i_ddi_irm_modify_increase(ddi_irm_req_t *req_p, int nreq)
{
dev_info_t *dip = req_p->ireq_dip;
ddi_irm_pool_t *pool_p = req_p->ireq_pool_p;
ddi_irm_req_t new_req;
int count, delta;
ASSERT(MUTEX_HELD(&pool_p->ipool_lock));
count = nreq - req_p->ireq_nreq;
if ((pool_p->ipool_minno + count) > pool_p->ipool_totsz) {
cmn_err(CE_WARN, "%s%d: interrupt pool too full.\n",
ddi_driver_name(dip), ddi_get_instance(dip));
return (DDI_EAGAIN);
}
pool_p->ipool_reqno += count;
pool_p->ipool_minno += count;
if ((pool_p->ipool_resno + count) <= pool_p->ipool_totsz) {
req_p->ireq_nreq += count;
req_p->ireq_navail += count;
pool_p->ipool_resno += count;
return (DDI_SUCCESS);
}
if ((pool_p->ipool_flags & DDI_IRM_FLAG_ACTIVE) == 0) {
pool_p->ipool_reqno -= count;
pool_p->ipool_minno -= count;
return (DDI_EAGAIN);
}
bzero(&new_req, sizeof (ddi_irm_req_t));
new_req.ireq_dip = dip;
new_req.ireq_nreq = count;
new_req.ireq_pool_p = pool_p;
new_req.ireq_type = req_p->ireq_type;
new_req.ireq_flags = DDI_IRM_FLAG_NEW;
i_ddi_irm_insertion_sort(&pool_p->ipool_req_list, &new_req);
i_ddi_irm_enqueue(pool_p, B_TRUE);
req_p->ireq_nreq += count;
if ((delta = (count - new_req.ireq_navail)) > 0) {
req_p->ireq_nreq -= delta;
pool_p->ipool_reqno -= delta;
pool_p->ipool_minno -= delta;
}
req_p->ireq_navail += new_req.ireq_navail;
list_remove(&pool_p->ipool_req_list, req_p);
list_remove(&pool_p->ipool_req_list, &new_req);
i_ddi_irm_insertion_sort(&pool_p->ipool_req_list, req_p);
return (DDI_SUCCESS);
}
int
i_ddi_irm_remove(dev_info_t *dip)
{
devinfo_intr_t *intr_p;
ddi_irm_pool_t *pool_p;
ddi_irm_req_t *req_p;
uint_t nmin;
ASSERT(dip != NULL);
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_remove: dip %p\n", (void *)dip));
if (dip == NULL) {
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_remove: invalid args\n"));
return (DDI_EINVAL);
}
if (!(intr_p = DEVI(dip)->devi_intr_p) ||
!(req_p = intr_p->devi_irm_req_p)) {
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_modify: not found\n"));
return (DDI_EINVAL);
}
pool_p = req_p->ireq_pool_p;
mutex_enter(&pool_p->ipool_lock);
nmin = DDI_IRM_IS_REDUCIBLE(req_p) ? 1 : req_p->ireq_nreq;
pool_p->ipool_minno -= nmin;
pool_p->ipool_reqno -= req_p->ireq_nreq;
pool_p->ipool_resno -= req_p->ireq_navail;
list_remove(&pool_p->ipool_req_list, req_p);
i_ddi_irm_enqueue(pool_p, B_FALSE);
mutex_exit(&pool_p->ipool_lock);
intr_p->devi_irm_req_p = NULL;
kmem_free(req_p, sizeof (ddi_irm_req_t));
return (DDI_SUCCESS);
}
void
i_ddi_irm_set_cb(dev_info_t *dip, boolean_t has_cb_flag)
{
devinfo_intr_t *intr_p;
ddi_irm_pool_t *pool_p;
ddi_irm_req_t *req_p;
uint_t nreq;
ASSERT(dip != NULL);
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_set_cb: dip %p has_cb_flag %d\n",
(void *)dip, (int)has_cb_flag));
if (dip == NULL)
return;
if (!(intr_p = DEVI(dip)->devi_intr_p) ||
!(req_p = intr_p->devi_irm_req_p)) {
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_set_cb: not in pool\n"));
return;
}
pool_p = req_p->ireq_pool_p;
mutex_enter(&pool_p->ipool_lock);
if (has_cb_flag) {
if (req_p->ireq_type == DDI_INTR_TYPE_MSIX)
pool_p->ipool_minno -= (req_p->ireq_nreq - 1);
req_p->ireq_flags |= DDI_IRM_FLAG_CALLBACK;
i_ddi_irm_enqueue(pool_p, B_FALSE);
} else {
nreq = MIN(req_p->ireq_nreq, pool_p->ipool_defsz);
#if defined(__x86)
if (req_p->ireq_type == DDI_INTR_TYPE_MSIX)
nreq = MIN(nreq, ddi_msix_alloc_limit);
#endif
pool_p->ipool_reqno -= req_p->ireq_nreq;
pool_p->ipool_reqno += nreq;
if (req_p->ireq_type == DDI_INTR_TYPE_MSIX) {
pool_p->ipool_minno -= 1;
pool_p->ipool_minno += nreq;
} else {
pool_p->ipool_minno -= req_p->ireq_nreq;
pool_p->ipool_minno += nreq;
}
req_p->ireq_nreq = nreq;
list_remove(&pool_p->ipool_req_list, req_p);
i_ddi_irm_insertion_sort(&pool_p->ipool_req_list, req_p);
i_ddi_irm_enqueue(pool_p, B_TRUE);
req_p->ireq_flags &= ~(DDI_IRM_FLAG_CALLBACK);
}
mutex_exit(&pool_p->ipool_lock);
}
int
i_ddi_irm_supported(dev_info_t *dip, int type)
{
ddi_cb_t *cb_p = DEVI(dip)->devi_cb_p;
return ((DDI_IRM_HAS_CB(cb_p) && (type == DDI_INTR_TYPE_MSIX)) ?
DDI_SUCCESS : DDI_ENOTSUP);
}
static void
irm_balance_thread(ddi_irm_pool_t *pool_p)
{
clock_t interval;
DDI_INTR_IRMDBG((CE_CONT, "irm_balance_thread: pool_p %p\n",
(void *)pool_p));
mutex_enter(&pool_p->ipool_lock);
if (pool_p->ipool_reqno > pool_p->ipool_resno)
i_ddi_irm_balance(pool_p);
pool_p->ipool_flags |= DDI_IRM_FLAG_ACTIVE;
for (;;) {
interval = drv_usectohz(irm_balance_delay * 1000000);
if ((interval > 0) &&
!(pool_p->ipool_flags & DDI_IRM_FLAG_WAITERS) &&
!(pool_p->ipool_flags & DDI_IRM_FLAG_EXIT)) {
(void) cv_reltimedwait(&pool_p->ipool_cv,
&pool_p->ipool_lock, interval, TR_CLOCK_TICK);
}
if (pool_p->ipool_flags & DDI_IRM_FLAG_EXIT) {
DDI_INTR_IRMDBG((CE_CONT,
"irm_balance_thread: exiting...\n"));
mutex_exit(&pool_p->ipool_lock);
thread_exit();
}
i_ddi_irm_balance(pool_p);
if (pool_p->ipool_flags & DDI_IRM_FLAG_WAITERS) {
cv_broadcast(&pool_p->ipool_cv);
pool_p->ipool_flags &= ~(DDI_IRM_FLAG_WAITERS);
}
pool_p->ipool_flags &= ~(DDI_IRM_FLAG_QUEUED);
cv_wait(&pool_p->ipool_cv, &pool_p->ipool_lock);
DDI_INTR_IRMDBG((CE_CONT, "irm_balance_thread: signaled.\n"));
}
}
static void
i_ddi_irm_balance(ddi_irm_pool_t *pool_p)
{
ddi_irm_req_t *req_p;
#ifdef DEBUG
uint_t debug_totsz = 0;
int debug_policy = 0;
#endif
ASSERT(pool_p != NULL);
ASSERT(MUTEX_HELD(&pool_p->ipool_lock));
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_balance: pool_p %p\n",
(void *)pool_p));
#ifndef DEBUG
if ((pool_p->ipool_reqno == pool_p->ipool_resno)) {
#else
if ((pool_p->ipool_reqno == pool_p->ipool_resno) && !irm_debug_size) {
#endif
DDI_INTR_IRMDBG((CE_CONT,
"i_ddi_irm_balance: pool already balanced\n"));
return;
}
#ifdef DEBUG
if (irm_debug_size > pool_p->ipool_minno) {
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_balance: debug size %d\n",
irm_debug_size));
debug_totsz = pool_p->ipool_totsz;
pool_p->ipool_totsz = irm_debug_size;
}
if (DDI_IRM_POLICY_VALID(irm_debug_policy)) {
DDI_INTR_IRMDBG((CE_CONT,
"i_ddi_irm_balance: debug policy %d\n", irm_debug_policy));
debug_policy = pool_p->ipool_policy;
pool_p->ipool_policy = irm_debug_policy;
}
#endif
mutex_enter(&pool_p->ipool_navail_lock);
for (req_p = list_head(&pool_p->ipool_req_list); req_p;
req_p = list_next(&pool_p->ipool_req_list, req_p)) {
if (DDI_IRM_IS_REDUCIBLE(req_p)) {
pool_p->ipool_resno -= req_p->ireq_navail;
req_p->ireq_scratch = req_p->ireq_navail;
req_p->ireq_navail = req_p->ireq_nreq;
pool_p->ipool_resno += req_p->ireq_navail;
list_insert_tail(&pool_p->ipool_scratch_list, req_p);
}
}
i_ddi_irm_reduce(pool_p);
mutex_exit(&pool_p->ipool_navail_lock);
req_p = list_head(&pool_p->ipool_scratch_list);
while (req_p) {
if ((req_p->ireq_navail < req_p->ireq_scratch) &&
(i_ddi_irm_notify(pool_p, req_p) != DDI_SUCCESS)) {
list_remove(&pool_p->ipool_scratch_list, req_p);
mutex_enter(&pool_p->ipool_navail_lock);
i_ddi_irm_reduce(pool_p);
mutex_exit(&pool_p->ipool_navail_lock);
req_p = list_head(&pool_p->ipool_scratch_list);
} else {
req_p = list_next(&pool_p->ipool_scratch_list, req_p);
}
}
while (req_p = list_remove_head(&pool_p->ipool_scratch_list)) {
if (req_p->ireq_navail > req_p->ireq_scratch) {
(void) i_ddi_irm_notify(pool_p, req_p);
}
}
#ifdef DEBUG
if (debug_totsz != 0)
pool_p->ipool_totsz = debug_totsz;
if (debug_policy != 0)
pool_p->ipool_policy = debug_policy;
#endif
}
static void
i_ddi_irm_reduce(ddi_irm_pool_t *pool_p)
{
int imbalance;
ASSERT(pool_p != NULL);
ASSERT(MUTEX_HELD(&pool_p->ipool_lock));
ASSERT(DDI_IRM_POLICY_VALID(pool_p->ipool_policy));
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_reduce: pool_p %p\n",
(void *)pool_p));
if ((imbalance = pool_p->ipool_resno - pool_p->ipool_totsz) <= 0)
return;
if (i_ddi_irm_reduce_by_policy(pool_p, imbalance, pool_p->ipool_policy)
!= DDI_SUCCESS) {
DDI_INTR_IRMDBG((CE_CONT,
"i_ddi_irm_reduce: policy reductions failed.\n"));
imbalance = pool_p->ipool_resno - pool_p->ipool_totsz;
ASSERT(imbalance > 0);
i_ddi_irm_reduce_new(pool_p, imbalance);
}
}
static void
i_ddi_irm_enqueue(ddi_irm_pool_t *pool_p, boolean_t wait_flag)
{
ASSERT(pool_p != NULL);
ASSERT(MUTEX_HELD(&pool_p->ipool_lock));
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_enqueue: pool_p %p wait_flag %d\n",
(void *)pool_p, (int)wait_flag));
#ifndef DEBUG
if ((pool_p->ipool_reqno == pool_p->ipool_resno)) {
#else
if ((pool_p->ipool_reqno == pool_p->ipool_resno) && !irm_debug_size) {
#endif
DDI_INTR_IRMDBG((CE_CONT,
"i_ddi_irm_enqueue: pool already balanced\n"));
return;
}
if (!irm_active && wait_flag) {
DDI_INTR_IRMDBG((CE_CONT,
"i_ddi_irm_enqueue: pool not active.\n"));
return;
}
if (wait_flag)
pool_p->ipool_flags |= DDI_IRM_FLAG_WAITERS;
if (wait_flag || !(pool_p->ipool_flags & DDI_IRM_FLAG_QUEUED)) {
pool_p->ipool_flags |= DDI_IRM_FLAG_QUEUED;
cv_signal(&pool_p->ipool_cv);
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_enqueue: pool queued.\n"));
}
if (wait_flag) {
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_enqueue: waiting...\n"));
cv_wait(&pool_p->ipool_cv, &pool_p->ipool_lock);
}
}
static int
i_ddi_irm_reduce_by_policy(ddi_irm_pool_t *pool_p, int imbalance, int policy)
{
ASSERT(pool_p != NULL);
ASSERT(imbalance > 0);
ASSERT(MUTEX_HELD(&pool_p->ipool_lock));
while (imbalance > 0) {
list_t *slist_p = &pool_p->ipool_scratch_list;
ddi_irm_req_t *req_p = list_head(slist_p), *last_p;
uint_t nreduce = 0, nremain = 0, stop_navail;
uint_t pool_defsz = pool_p->ipool_defsz;
uint_t reduction, max_redu;
if (!req_p || req_p->ireq_navail <= pool_defsz) {
DDI_INTR_IRMDBG((CE_CONT,
"i_ddi_irm_reduce_by_policy: Failure. "
"All requests have downsized to low limit.\n"));
return (DDI_FAILURE);
}
stop_navail = (policy == DDI_IRM_POLICY_LARGE) ?
req_p->ireq_navail - 1 : pool_defsz;
for (; req_p; req_p = list_next(slist_p, req_p)) {
if (req_p->ireq_navail <= stop_navail)
break;
nreduce++;
}
last_p = req_p ? list_prev(slist_p, req_p) : list_tail(slist_p);
if ((policy == DDI_IRM_POLICY_LARGE) && req_p &&
req_p->ireq_navail > pool_defsz)
reduction = last_p->ireq_navail - req_p->ireq_navail;
else
reduction = last_p->ireq_navail - pool_defsz;
if ((max_redu = reduction * nreduce) > imbalance) {
reduction = imbalance / nreduce;
nremain = imbalance % nreduce;
pool_p->ipool_resno -= imbalance;
imbalance = 0;
} else {
pool_p->ipool_resno -= max_redu;
imbalance -= max_redu;
}
for (req_p = list_head(slist_p); (reduction != 0) && nreduce--;
req_p = list_next(slist_p, req_p)) {
req_p->ireq_navail -= reduction;
}
for (req_p = last_p; nremain--;
req_p = list_prev(slist_p, req_p)) {
req_p->ireq_navail--;
}
}
return (DDI_SUCCESS);
}
static void
i_ddi_irm_reduce_new(ddi_irm_pool_t *pool_p, int imbalance)
{
ddi_irm_req_t *req_p;
ASSERT(pool_p != NULL);
ASSERT(imbalance > 0);
ASSERT(MUTEX_HELD(&pool_p->ipool_lock));
DDI_INTR_IRMDBG((CE_CONT,
"i_ddi_irm_reduce_new: pool_p %p imbalance %d\n",
(void *)pool_p, imbalance));
for (req_p = list_head(&pool_p->ipool_scratch_list); req_p;
req_p = list_next(&pool_p->ipool_scratch_list, req_p)) {
if (req_p->ireq_flags & DDI_IRM_FLAG_NEW) {
ASSERT(req_p->ireq_navail >= imbalance);
req_p->ireq_navail -= imbalance;
pool_p->ipool_resno -= imbalance;
return;
}
}
ASSERT(B_FALSE);
}
ddi_irm_pool_t *
i_ddi_intr_get_pool(dev_info_t *dip, int type)
{
devinfo_intr_t *intr_p;
ddi_irm_pool_t *pool_p;
ddi_irm_req_t *req_p;
ddi_intr_handle_impl_t hdl;
ASSERT(dip != NULL);
ASSERT(DDI_INTR_TYPE_FLAG_VALID(type));
if (((intr_p = DEVI(dip)->devi_intr_p) != NULL) &&
((req_p = intr_p->devi_irm_req_p) != NULL) &&
((pool_p = req_p->ireq_pool_p) != NULL) &&
(pool_p->ipool_types & type)) {
return (pool_p);
}
bzero(&hdl, sizeof (ddi_intr_handle_impl_t));
hdl.ih_dip = dip;
hdl.ih_type = type;
if (i_ddi_intr_ops(dip, dip, DDI_INTROP_GETPOOL,
&hdl, (void *)&pool_p) == DDI_SUCCESS)
return (pool_p);
return (NULL);
}
static void
i_ddi_irm_insertion_sort(list_t *req_list, ddi_irm_req_t *req_p)
{
ddi_irm_req_t *next_p;
next_p = list_head(req_list);
while (next_p && (next_p->ireq_nreq > req_p->ireq_nreq))
next_p = list_next(req_list, next_p);
list_insert_before(req_list, next_p, req_p);
}
static int
i_ddi_irm_notify(ddi_irm_pool_t *pool_p, ddi_irm_req_t *req_p)
{
ddi_cb_action_t action;
ddi_cb_t *cb_p;
uint_t nintrs;
int ret, count;
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_notify: pool_p %p req_p %p\n",
(void *)pool_p, (void *)req_p));
if ((req_p->ireq_navail == req_p->ireq_scratch) ||
(req_p->ireq_flags & DDI_IRM_FLAG_NEW))
return (DDI_SUCCESS);
if (req_p->ireq_navail > req_p->ireq_scratch) {
action = DDI_CB_INTR_ADD;
count = req_p->ireq_navail - req_p->ireq_scratch;
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_notify: adding %d\n",
count));
} else {
action = DDI_CB_INTR_REMOVE;
count = req_p->ireq_scratch - req_p->ireq_navail;
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_notify: removing %d\n",
count));
}
if ((cb_p = DEVI(req_p->ireq_dip)->devi_cb_p) == NULL) {
DDI_INTR_IRMDBG((CE_WARN, "i_ddi_irm_notify: no callback!\n"));
return (DDI_FAILURE);
}
ret = cb_p->cb_func(req_p->ireq_dip, action, (void *)(uintptr_t)count,
cb_p->cb_arg1, cb_p->cb_arg2);
if (ret != DDI_SUCCESS) {
cmn_err(CE_WARN, "%s%d: failed callback (action=%d, ret=%d)\n",
ddi_driver_name(req_p->ireq_dip),
ddi_get_instance(req_p->ireq_dip), (int)action, ret);
}
nintrs = i_ddi_intr_get_current_nintrs(req_p->ireq_dip);
if (nintrs > req_p->ireq_navail) {
cmn_err(CE_WARN, "%s%d: failed to release interrupts "
"(nintrs=%d, navail=%d).\n",
ddi_driver_name(req_p->ireq_dip),
ddi_get_instance(req_p->ireq_dip), nintrs,
req_p->ireq_navail);
pool_p->ipool_resno += (nintrs - req_p->ireq_navail);
req_p->ireq_navail = nintrs;
return (DDI_FAILURE);
}
req_p->ireq_scratch = req_p->ireq_navail;
return (DDI_SUCCESS);
}
#ifdef DEBUG
void
i_ddi_irm_debug_balance(dev_info_t *dip, boolean_t wait_flag)
{
ddi_irm_pool_t *pool_p;
int type;
DDI_INTR_IRMDBG((CE_CONT, "i_ddi_irm_debug_balance: dip %p wait %d\n",
(void *)dip, (int)wait_flag));
if (((type = i_ddi_intr_get_current_type(dip)) != 0) &&
((pool_p = i_ddi_intr_get_pool(dip, type)) != NULL)) {
mutex_enter(&pool_p->ipool_lock);
i_ddi_irm_enqueue(pool_p, wait_flag);
mutex_exit(&pool_p->ipool_lock);
}
}
#endif