#include <sys/param.h>
#include <sys/cmn_err.h>
#include <sys/trap.h>
#include <sys/t_lock.h>
#include <sys/avintr.h>
#include <sys/kmem.h>
#include <sys/machlock.h>
#include <sys/systm.h>
#include <sys/machsystm.h>
#include <sys/sunddi.h>
#include <sys/x_call.h>
#include <sys/cpuvar.h>
#include <sys/atomic.h>
#include <sys/smp_impldefs.h>
#include <sys/sdt.h>
#include <sys/stack.h>
#include <sys/ddi_impldefs.h>
#ifdef __xpv
#include <sys/evtchn_impl.h>
#endif
typedef struct av_softinfo {
cpuset_t av_pending;
} av_softinfo_t;
static void insert_av(void *intr_id, struct av_head *vectp, avfunc f,
caddr_t arg1, caddr_t arg2, uint64_t *ticksp, int pri_level,
dev_info_t *dip);
static void remove_av(void *intr_id, struct av_head *vectp, avfunc f,
int pri_level, int vect);
static char badsoft[] =
"add_avintr: bad soft interrupt level %d for driver '%s'\n";
static char multilevel[] =
"!IRQ%d is being shared by drivers with different interrupt levels.\n"
"This may result in reduced system performance.";
static char multilevel2[] =
"Cannot register interrupt for '%s' device at IPL %d because it\n"
"conflicts with another device using the same vector %d with an IPL\n"
"of %d. Reconfigure the conflicting devices to use different vectors.";
#ifdef __xpv
#define MAX_VECT NR_IRQS
#else
#define MAX_VECT 256
#endif
struct autovec *nmivect = NULL;
struct av_head autovect[MAX_VECT];
struct av_head softvect[LOCK_LEVEL + 1];
kmutex_t av_lock;
ddi_softint_hdl_impl_t softlevel_hdl[DDI_IPL_10] = {
{0, 0, NULL, NULL, 0, NULL, NULL, NULL},
{0, 0, NULL, NULL, 0, NULL, NULL, NULL},
{0, 0, NULL, NULL, 0, NULL, NULL, NULL},
{0, 0, NULL, NULL, 0, NULL, NULL, NULL},
{0, 0, NULL, NULL, 0, NULL, NULL, NULL},
{0, 0, NULL, NULL, 0, NULL, NULL, NULL},
{0, 0, NULL, NULL, 0, NULL, NULL, NULL},
{0, 0, NULL, NULL, 0, NULL, NULL, NULL},
{0, 0, NULL, NULL, 0, NULL, NULL, NULL},
{0, 0, NULL, NULL, 0, NULL, NULL, NULL},
};
ddi_softint_hdl_impl_t softlevel1_hdl =
{0, 0, NULL, NULL, 0, NULL, NULL, NULL};
void
av_clear_softint_pending(av_softinfo_t *infop)
{
CPUSET_ATOMIC_DEL(infop->av_pending, CPU->cpu_seqid);
}
boolean_t
av_check_softint_pending(av_softinfo_t *infop, boolean_t check_all)
{
if (check_all)
return (!CPUSET_ISNULL(infop->av_pending));
else
return (CPU_IN_SET(infop->av_pending, CPU->cpu_seqid) != 0);
}
void
av_set_softint_pending(int pri, av_softinfo_t *infop)
{
kdi_av_set_softint_pending(pri, infop);
}
void
kdi_av_set_softint_pending(int pri, av_softinfo_t *infop)
{
CPUSET_ATOMIC_ADD(infop->av_pending, CPU->cpu_seqid);
atomic_or_32((uint32_t *)&CPU->cpu_softinfo.st_pending, 1 << pri);
}
int
add_nmintr(int lvl, avfunc nmintr, char *name, caddr_t arg)
{
struct autovec *mem;
struct autovec *p, *prev = NULL;
if (nmintr == NULL) {
printf("Attempt to add null vect for %s on nmi\n", name);
return (0);
}
mem = kmem_zalloc(sizeof (struct autovec), KM_SLEEP);
mem->av_vector = nmintr;
mem->av_intarg1 = arg;
mem->av_intarg2 = NULL;
mem->av_intr_id = NULL;
mem->av_prilevel = lvl;
mem->av_dip = NULL;
mem->av_link = NULL;
mutex_enter(&av_lock);
if (!nmivect) {
nmivect = mem;
mutex_exit(&av_lock);
return (1);
}
for (p = nmivect; p != NULL; p = p->av_link) {
if (p->av_vector == nmintr && p->av_intarg1 == arg) {
cmn_err(CE_WARN, "Driver already registered '%s'",
name);
kmem_free(mem, sizeof (struct autovec));
mutex_exit(&av_lock);
return (0);
}
if (p->av_prilevel < lvl) {
if (p == nmivect) {
mem->av_link = p;
nmivect = mem;
} else {
mem->av_link = p;
prev->av_link = mem;
}
mutex_exit(&av_lock);
return (1);
}
prev = p;
}
prev->av_link = mem;
mutex_exit(&av_lock);
return (1);
}
int
add_avintr(void *intr_id, int lvl, avfunc xxintr, char *name, int vect,
caddr_t arg1, caddr_t arg2, uint64_t *ticksp, dev_info_t *dip)
{
struct av_head *vecp = (struct av_head *)0;
avfunc f;
int s, vectindex;
ushort_t hi_pri;
if (addintr) {
return ((*addintr)(intr_id, lvl, xxintr, name, vect,
arg1, arg2, ticksp, dip));
}
if ((f = xxintr) == NULL) {
printf("Attempt to add null vect for %s on vector %d\n",
name, vect);
return (0);
}
vectindex = vect % MAX_VECT;
vecp = &autovect[vectindex];
hi_pri = vecp->avh_hi_pri;
if (vecp->avh_link && (hi_pri != 0)) {
if (((hi_pri > LOCK_LEVEL) && (lvl < LOCK_LEVEL)) ||
((hi_pri < LOCK_LEVEL) && (lvl > LOCK_LEVEL))) {
cmn_err(CE_WARN, multilevel2, name, lvl, vect,
hi_pri);
return (0);
}
if ((vecp->avh_lo_pri != lvl) || (hi_pri != lvl))
cmn_err(CE_NOTE, multilevel, vect);
}
insert_av(intr_id, vecp, f, arg1, arg2, ticksp, lvl, dip);
s = splhi();
mutex_enter(&av_lock);
(*addspl)(vect, lvl, vecp->avh_lo_pri, vecp->avh_hi_pri);
mutex_exit(&av_lock);
splx(s);
return (1);
}
void
update_avsoftintr_args(void *intr_id, int lvl, caddr_t arg2)
{
struct autovec *p;
struct autovec *target = NULL;
struct av_head *vectp = (struct av_head *)&softvect[lvl];
for (p = vectp->avh_link; p && p->av_vector; p = p->av_link) {
if (p->av_intr_id == intr_id) {
target = p;
break;
}
}
if (target == NULL)
return;
target->av_intarg2 = arg2;
}
int
add_avsoftintr(void *intr_id, int lvl, avfunc xxintr, char *name,
caddr_t arg1, caddr_t arg2)
{
int slvl;
ddi_softint_hdl_impl_t *hdlp = (ddi_softint_hdl_impl_t *)intr_id;
if ((slvl = slvltovect(lvl)) != -1)
return (add_avintr(intr_id, lvl, xxintr,
name, slvl, arg1, arg2, NULL, NULL));
if (intr_id == NULL) {
printf("Attempt to add null intr_id for %s on level %d\n",
name, lvl);
return (0);
}
if (xxintr == NULL) {
printf("Attempt to add null handler for %s on level %d\n",
name, lvl);
return (0);
}
if (lvl <= 0 || lvl > LOCK_LEVEL) {
printf(badsoft, lvl, name);
return (0);
}
if (hdlp->ih_pending == NULL) {
hdlp->ih_pending =
kmem_zalloc(sizeof (av_softinfo_t), KM_SLEEP);
}
insert_av(intr_id, &softvect[lvl], xxintr, arg1, arg2, NULL, lvl, NULL);
return (1);
}
static void
insert_av(void *intr_id, struct av_head *vectp, avfunc f, caddr_t arg1,
caddr_t arg2, uint64_t *ticksp, int pri_level, dev_info_t *dip)
{
struct autovec *p, *prep, *mem;
mem = kmem_zalloc(sizeof (struct autovec), KM_SLEEP);
mem->av_vector = f;
mem->av_intarg1 = arg1;
mem->av_intarg2 = arg2;
mem->av_ticksp = ticksp;
mem->av_intr_id = intr_id;
mem->av_prilevel = pri_level;
mem->av_dip = dip;
mem->av_link = NULL;
mutex_enter(&av_lock);
if (vectp->avh_link == NULL) {
vectp->avh_link = mem;
vectp->avh_hi_pri = vectp->avh_lo_pri = (ushort_t)pri_level;
mutex_exit(&av_lock);
return;
}
prep = NULL;
for (p = vectp->avh_link; p != NULL; p = p->av_link) {
if (p->av_vector && p->av_prilevel <= pri_level)
break;
prep = p;
}
if (prep != NULL) {
if (prep->av_vector == NULL) {
p = prep;
p->av_intarg1 = arg1;
p->av_intarg2 = arg2;
p->av_ticksp = ticksp;
p->av_intr_id = intr_id;
p->av_prilevel = pri_level;
p->av_dip = dip;
if (pri_level > (int)vectp->avh_hi_pri) {
vectp->avh_hi_pri = (ushort_t)pri_level;
}
if (pri_level < (int)vectp->avh_lo_pri) {
vectp->avh_lo_pri = (ushort_t)pri_level;
}
p->av_vector = f;
mutex_exit(&av_lock);
kmem_free(mem, sizeof (struct autovec));
return;
}
mem->av_link = prep->av_link;
prep->av_link = mem;
} else {
mem->av_link = vectp->avh_link;
vectp->avh_link = mem;
}
if (pri_level > (int)vectp->avh_hi_pri) {
vectp->avh_hi_pri = (ushort_t)pri_level;
}
if (pri_level < (int)vectp->avh_lo_pri) {
vectp->avh_lo_pri = (ushort_t)pri_level;
}
mutex_exit(&av_lock);
}
static int
av_rem_softintr(void *intr_id, int lvl, avfunc xxintr, boolean_t rem_softinfo)
{
struct av_head *vecp = (struct av_head *)0;
int slvl;
ddi_softint_hdl_impl_t *hdlp = (ddi_softint_hdl_impl_t *)intr_id;
av_softinfo_t *infop = (av_softinfo_t *)hdlp->ih_pending;
if (xxintr == NULL)
return (0);
if ((slvl = slvltovect(lvl)) != -1) {
rem_avintr(intr_id, lvl, xxintr, slvl);
return (1);
}
if (lvl <= 0 && lvl >= LOCK_LEVEL) {
return (0);
}
vecp = &softvect[lvl];
remove_av(intr_id, vecp, xxintr, lvl, 0);
if (rem_softinfo) {
kmem_free(infop, sizeof (av_softinfo_t));
hdlp->ih_pending = NULL;
}
return (1);
}
int
av_softint_movepri(void *intr_id, int old_lvl)
{
int ret;
ddi_softint_hdl_impl_t *hdlp = (ddi_softint_hdl_impl_t *)intr_id;
ret = add_avsoftintr(intr_id, hdlp->ih_pri, hdlp->ih_cb_func,
DEVI(hdlp->ih_dip)->devi_name, hdlp->ih_cb_arg1, hdlp->ih_cb_arg2);
if (ret) {
(void) av_rem_softintr(intr_id, old_lvl, hdlp->ih_cb_func,
B_FALSE);
}
return (ret);
}
int
rem_avsoftintr(void *intr_id, int lvl, avfunc xxintr)
{
return (av_rem_softintr(intr_id, lvl, xxintr, B_TRUE));
}
void
rem_avintr(void *intr_id, int lvl, avfunc xxintr, int vect)
{
struct av_head *vecp = (struct av_head *)0;
avfunc f;
int s, vectindex;
if (remintr) {
(*remintr)(intr_id, lvl, xxintr, vect);
return;
}
if ((f = xxintr) == NULL)
return;
vectindex = vect % MAX_VECT;
vecp = &autovect[vectindex];
remove_av(intr_id, vecp, f, lvl, vect);
s = splhi();
mutex_enter(&av_lock);
(*delspl)(vect, lvl, vecp->avh_lo_pri, vecp->avh_hi_pri);
mutex_exit(&av_lock);
splx(s);
}
void
wait_till_seen(int ipl)
{
int cpu_in_chain, cix;
struct cpu *cpup;
cpuset_t cpus_to_check;
CPUSET_ALL(cpus_to_check);
do {
cpu_in_chain = 0;
for (cix = 0; cix < NCPU; cix++) {
cpup = cpu[cix];
if (cpup != NULL && CPU_IN_SET(cpus_to_check, cix)) {
if (INTR_ACTIVE(cpup, ipl)) {
cpu_in_chain = 1;
} else {
CPUSET_DEL(cpus_to_check, cix);
}
}
}
} while (cpu_in_chain);
}
static uint64_t dummy_tick;
static void
remove_av(void *intr_id, struct av_head *vectp, avfunc f, int pri_level,
int vect)
{
struct autovec *p, *target;
int lo_pri, hi_pri;
int ipl;
target = NULL;
mutex_enter(&av_lock);
ipl = pri_level;
lo_pri = MAXIPL;
hi_pri = 0;
for (p = vectp->avh_link; p; p = p->av_link) {
if ((p->av_vector == f) && (p->av_intr_id == intr_id)) {
target = p;
continue;
}
if (p->av_vector != NULL) {
if (p->av_prilevel > hi_pri)
hi_pri = p->av_prilevel;
if (p->av_prilevel < lo_pri)
lo_pri = p->av_prilevel;
}
}
if (ipl < hi_pri)
ipl = hi_pri;
if (target == NULL) {
printf("Couldn't remove function %p at %d, %d\n",
(void *)f, vect, pri_level);
mutex_exit(&av_lock);
return;
}
target->av_vector = NULL;
target->av_ticksp = &dummy_tick;
wait_till_seen(ipl);
if (lo_pri > hi_pri) {
vectp->avh_lo_pri = MAXIPL;
vectp->avh_hi_pri = 0;
} else {
if ((int)vectp->avh_lo_pri < lo_pri)
vectp->avh_lo_pri = (ushort_t)lo_pri;
if ((int)vectp->avh_hi_pri > hi_pri)
vectp->avh_hi_pri = (ushort_t)hi_pri;
}
mutex_exit(&av_lock);
wait_till_seen(ipl);
}
void
kdi_siron(void)
{
(*kdisetsoftint)(1, softlevel1_hdl.ih_pending);
}
void
siron(void)
{
(*setsoftint)(1, softlevel1_hdl.ih_pending);
}
void
sir_on(int level)
{
ASSERT(level >= DDI_IPL_1 && level <= DDI_IPL_10);
(*setsoftint)(level, softlevel_hdl[level-1].ih_pending);
}
static int
siron_poke_intr(xc_arg_t a1, xc_arg_t a2, xc_arg_t a3)
{
siron();
return (0);
}
void
siron_poke_cpu(cpuset_t poke)
{
int cpuid = CPU->cpu_id;
if (CPU_IN_SET(poke, cpuid)) {
siron();
CPUSET_DEL(poke, cpuid);
if (CPUSET_ISNULL(poke))
return;
}
xc_call(0, 0, 0, CPUSET2BV(poke), (xc_func_t)siron_poke_intr);
}
extern uint64_t intr_get_time(void);
void
av_dispatch_autovect(uint_t vec)
{
struct autovec *av;
ASSERT_STACK_ALIGNED();
while ((av = autovect[vec].avh_link) != NULL) {
uint_t numcalled = 0;
uint_t claimed = 0;
for (; av; av = av->av_link) {
uint_t r;
uint_t (*intr)() = av->av_vector;
caddr_t arg1 = av->av_intarg1;
caddr_t arg2 = av->av_intarg2;
dev_info_t *dip = av->av_dip;
if (intr == NULL)
continue;
DTRACE_PROBE4(interrupt__start, dev_info_t *, dip,
void *, intr, caddr_t, arg1, caddr_t, arg2);
r = (*intr)(arg1, arg2);
DTRACE_PROBE4(interrupt__complete, dev_info_t *, dip,
void *, intr, caddr_t, arg1, uint_t, r);
numcalled++;
claimed |= r;
if (av->av_ticksp && av->av_prilevel <= LOCK_LEVEL)
atomic_add_64(av->av_ticksp, intr_get_time());
}
if (numcalled == 1 || claimed == 0)
break;
}
}
void
av_dispatch_softvect(uint_t pil)
{
struct autovec *av;
ddi_softint_hdl_impl_t *hdlp;
uint_t (*intr)();
caddr_t arg1;
caddr_t arg2;
ASSERT_STACK_ALIGNED();
ASSERT3U(pil, <=, PIL_MAX);
for (av = softvect[pil].avh_link; av; av = av->av_link) {
if ((intr = av->av_vector) == NULL)
continue;
arg1 = av->av_intarg1;
arg2 = av->av_intarg2;
hdlp = (ddi_softint_hdl_impl_t *)av->av_intr_id;
ASSERT(hdlp);
if (av_check_softint_pending(hdlp->ih_pending, B_FALSE)) {
av_clear_softint_pending(hdlp->ih_pending);
(void) (*intr)(arg1, arg2);
}
}
}
struct regs;
void
av_dispatch_nmivect(struct regs *rp)
{
struct autovec *av;
ASSERT_STACK_ALIGNED();
for (av = nmivect; av; av = av->av_link)
(void) (av->av_vector)(av->av_intarg1, rp);
}