#include <sys/types.h>
#include <sys/xpv_support.h>
#include <sys/hypervisor.h>
#include <sys/machsystm.h>
#include <sys/mutex.h>
#include <sys/cmn_err.h>
#include <sys/dditypes.h>
#include <sys/atomic.h>
#include <sys/sysmacros.h>
#include <sys/cpu.h>
#include <sys/psw.h>
#include <sys/psm.h>
#include <sys/sdt.h>
extern dev_info_t *xpv_dip;
static ddi_intr_handle_t *evtchn_ihp = NULL;
static ddi_softint_handle_t evtchn_to_handle[NR_EVENT_CHANNELS];
kmutex_t ec_lock;
static int evtchn_callback_irq = -1;
static volatile ulong_t *pending_events;
static volatile ulong_t *masked_events;
#define EVTCHN_SHIFT 6
#define GET_AND_CLEAR(src, targ) { \
membar_enter(); \
do { \
targ = *src; \
} while (atomic_cas_ulong(src, targ, 0) != targ); \
}
#define GET_BOUNDS(bitmap, low, high) { \
int _i; \
low = high = -1; \
for (_i = 0; _i <= sizeof (ulong_t); _i++) \
if (bitmap & (1UL << _i)) { \
if (low == -1) \
low = _i; \
high = _i; \
} \
}
void
ec_bind_evtchn_to_handler(int evtchn, pri_t pri, ec_handler_fcn_t handler,
void *arg1)
{
ddi_softint_handle_t hdl;
if (evtchn < 0 || evtchn >= NR_EVENT_CHANNELS) {
cmn_err(CE_WARN, "Binding invalid event channel: %d", evtchn);
return;
}
(void) ddi_intr_add_softint(xpv_dip, &hdl, pri, handler, (caddr_t)arg1);
mutex_enter(&ec_lock);
ASSERT(evtchn_to_handle[evtchn] == NULL);
evtchn_to_handle[evtchn] = hdl;
mutex_exit(&ec_lock);
hypervisor_unmask_event(evtchn);
}
void
ec_unbind_evtchn(int evtchn)
{
evtchn_close_t close;
ddi_softint_handle_t hdl;
if (evtchn < 0 || evtchn >= NR_EVENT_CHANNELS) {
cmn_err(CE_WARN, "Unbinding invalid event channel: %d", evtchn);
return;
}
hypervisor_mask_event(evtchn);
mutex_enter(&ec_lock);
hdl = evtchn_to_handle[evtchn];
evtchn_to_handle[evtchn] = NULL;
mutex_exit(&ec_lock);
close.port = evtchn;
(void) HYPERVISOR_event_channel_op(EVTCHNOP_close, &close);
(void) ddi_intr_remove_softint(hdl);
}
void
ec_notify_via_evtchn(unsigned int port)
{
evtchn_send_t send;
if ((int)port == -1)
return;
send.port = port;
(void) HYPERVISOR_event_channel_op(EVTCHNOP_send, &send);
}
void
hypervisor_unmask_event(unsigned int ev)
{
int index = ev >> EVTCHN_SHIFT;
ulong_t bit = 1UL << (ev & ((1UL << EVTCHN_SHIFT) - 1));
volatile ulong_t *maskp;
evtchn_unmask_t unmask;
maskp = &masked_events[index];
atomic_and_ulong(maskp, ~bit);
unmask.port = ev;
if (HYPERVISOR_event_channel_op(EVTCHNOP_unmask, &unmask) != 0)
panic("xen_evtchn_unmask() failed");
}
void
hypervisor_mask_event(uint_t ev)
{
int index = ev >> EVTCHN_SHIFT;
ulong_t bit = 1UL << (ev & ((1UL << EVTCHN_SHIFT) - 1));
volatile ulong_t *maskp;
maskp = &masked_events[index];
atomic_or_ulong(maskp, bit);
}
void
hypervisor_clear_event(uint_t ev)
{
int index = ev >> EVTCHN_SHIFT;
ulong_t bit = 1UL << (ev & ((1UL << EVTCHN_SHIFT) - 1));
volatile ulong_t *maskp;
maskp = &pending_events[index];
atomic_and_ulong(maskp, ~bit);
}
int
xen_alloc_unbound_evtchn(int domid, int *evtchnp)
{
evtchn_alloc_unbound_t alloc;
int err;
alloc.dom = DOMID_SELF;
alloc.remote_dom = (domid_t)domid;
if ((err = HYPERVISOR_event_channel_op(EVTCHNOP_alloc_unbound,
&alloc)) == 0) {
*evtchnp = alloc.port;
(void) hypervisor_mask_event(*evtchnp);
} else {
err = xen_xlate_errcode(err);
}
return (err);
}
int
xen_bind_interdomain(int domid, int remote_port, int *port)
{
evtchn_bind_interdomain_t bind;
int err;
bind.remote_dom = (domid_t)domid;
bind.remote_port = remote_port;
if ((err = HYPERVISOR_event_channel_op(EVTCHNOP_bind_interdomain,
&bind)) == 0)
*port = bind.local_port;
else
err = xen_xlate_errcode(err);
return (err);
}
uint_t
evtchn_callback_fcn(caddr_t arg0, caddr_t arg1)
{
ulong_t pending_word;
int i, j, port;
volatile struct vcpu_info *vci;
uint_t rv = DDI_INTR_UNCLAIMED;
ddi_softint_handle_t hdl;
int low, high;
ulong_t sels;
ASSERT(CPU->cpu_id == 0);
vci = &HYPERVISOR_shared_info->vcpu_info[0];
again:
DTRACE_PROBE2(evtchn__scan__start, int, vci->evtchn_upcall_pending,
ulong_t, vci->evtchn_pending_sel);
atomic_and_8(&vci->evtchn_upcall_pending, 0);
GET_AND_CLEAR(&vci->evtchn_pending_sel, sels);
if (sels == 1)
low = high = 0;
else if (sels == 0)
return (rv);
else
GET_BOUNDS(sels, low, high);
for (i = low; i <= high; i++) {
ulong_t tmp;
GET_AND_CLEAR(&pending_events[i], tmp);
pending_word = tmp & ~(masked_events[i]);
while (pending_word != 0) {
j = lowbit(pending_word) - 1;
port = (i << EVTCHN_SHIFT) + j;
pending_word = pending_word & ~(1UL << j);
if ((hdl = evtchn_to_handle[port]) != NULL) {
(void) ddi_intr_trigger_softint(hdl, NULL);
rv = DDI_INTR_CLAIMED;
}
}
}
DTRACE_PROBE2(evtchn__scan__end, int, vci->evtchn_upcall_pending,
ulong_t, vci->evtchn_pending_sel);
if ((volatile uint8_t)vci->evtchn_upcall_pending ||
((volatile ulong_t)vci->evtchn_pending_sel))
goto again;
return (rv);
}
static int
set_hvm_callback(int irq)
{
struct xen_hvm_param xhp;
xhp.domid = DOMID_SELF;
xhp.index = HVM_PARAM_CALLBACK_IRQ;
xhp.value = irq;
return (HYPERVISOR_hvm_op(HVMOP_set_param, &xhp));
}
void
ec_fini()
{
int i;
for (i = 0; i < NR_EVENT_CHANNELS; i++)
ec_unbind_evtchn(i);
evtchn_callback_irq = -1;
if (evtchn_ihp != NULL) {
(void) ddi_intr_disable(*evtchn_ihp);
(void) ddi_intr_remove_handler(*evtchn_ihp);
(void) ddi_intr_free(*evtchn_ihp);
kmem_free(evtchn_ihp, sizeof (ddi_intr_handle_t));
evtchn_ihp = NULL;
}
}
int
ec_init(void)
{
int i;
int rv, actual;
ddi_intr_handle_t *ihp;
pending_events = &HYPERVISOR_shared_info->evtchn_pending[0];
masked_events = &HYPERVISOR_shared_info->evtchn_mask[0];
mutex_init(&ec_lock, NULL, MUTEX_SPIN, (void *)ipltospl(SPL7));
for (i = 0; i < NR_EVENT_CHANNELS; i++) {
evtchn_to_handle[i] = NULL;
(void) hypervisor_mask_event(i);
}
ihp = kmem_zalloc(sizeof (ddi_intr_handle_t), KM_SLEEP);
rv = ddi_intr_alloc(xpv_dip, ihp, DDI_INTR_TYPE_FIXED, 0, 1, &actual,
DDI_INTR_ALLOC_NORMAL);
if (rv < 0 || actual != 1) {
cmn_err(CE_WARN, "Could not allocate evtchn interrupt: %d",
rv);
return (-1);
}
rv = ddi_intr_add_handler(*ihp, evtchn_callback_fcn, NULL, NULL);
if (rv < 0) {
(void) ddi_intr_free(*ihp);
cmn_err(CE_WARN, "Could not attach evtchn handler");
return (-1);
}
evtchn_ihp = ihp;
if (ddi_intr_enable(*ihp) != DDI_SUCCESS) {
cmn_err(CE_WARN, "Could not enable evtchn interrupts\n");
return (-1);
}
evtchn_callback_irq = ((ddi_intr_handle_impl_t *)*ihp)->ih_vector;
if (set_hvm_callback(evtchn_callback_irq) != 0) {
cmn_err(CE_WARN, "Couldn't register evtchn callback");
return (-1);
}
return (0);
}
void
ec_resume(void)
{
int i;
for (i = 0; i < NR_EVENT_CHANNELS; i++)
(void) hypervisor_mask_event(i);
if (set_hvm_callback(evtchn_callback_irq) != 0)
cmn_err(CE_WARN, "Couldn't register evtchn callback");
}