#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: xenevt.c,v 1.68 2022/09/22 16:21:34 bouyer Exp $");
#include "opt_xen.h"
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/mutex.h>
#include <sys/systm.h>
#include <sys/device.h>
#include <sys/file.h>
#include <sys/filedesc.h>
#include <sys/poll.h>
#include <sys/select.h>
#include <sys/proc.h>
#include <sys/conf.h>
#include <sys/intr.h>
#include <sys/kmem.h>
#include <uvm/uvm_extern.h>
#include <xen/hypervisor.h>
#include <xen/evtchn.h>
#include <xen/intr.h>
#ifdef XENPV
#include <xen/xenpmap.h>
#endif
#include <xen/xenio.h>
#include <xen/xenio3.h>
#include <xen/xen.h>
#include "ioconf.h"
static int xenevt_fread(struct file *, off_t *, struct uio *,
kauth_cred_t, int);
static int xenevt_fwrite(struct file *, off_t *, struct uio *,
kauth_cred_t, int);
static int xenevt_fioctl(struct file *, u_long, void *);
static int xenevt_fpoll(struct file *, int);
static int xenevt_fclose(struct file *);
static const struct fileops xenevt_fileops = {
.fo_name = "xenevt",
.fo_read = xenevt_fread,
.fo_write = xenevt_fwrite,
.fo_ioctl = xenevt_fioctl,
.fo_fcntl = fnullop_fcntl,
.fo_poll = xenevt_fpoll,
.fo_stat = fbadop_stat,
.fo_close = xenevt_fclose,
.fo_kqfilter = fnullop_kqfilter,
.fo_restart = fnullop_restart,
};
dev_type_open(xenevtopen);
dev_type_read(xenevtread);
dev_type_mmap(xenevtmmap);
const struct cdevsw xenevt_cdevsw = {
.d_open = xenevtopen,
.d_close = nullclose,
.d_read = xenevtread,
.d_write = nowrite,
.d_ioctl = noioctl,
.d_stop = nostop,
.d_tty = notty,
.d_poll = nopoll,
.d_mmap = xenevtmmap,
.d_kqfilter = nokqfilter,
.d_discard = nodiscard,
.d_flag = D_OTHER
};
#define DEV_EVT 0
#define DEV_XSD 1
#define XENEVT_RING_SIZE 2048
#define XENEVT_RING_MASK 2047
#define BYTES_PER_PORT (sizeof(evtchn_port_t) / sizeof(uint8_t))
struct xenevt_d {
kmutex_t lock;
kcondvar_t cv;
STAILQ_ENTRY(xenevt_d) pendingq;
bool pending;
evtchn_port_t ring[2048];
u_int ring_read;
u_int ring_write;
u_int flags;
#define XENEVT_F_OVERFLOW 0x01
#define XENEVT_F_FREE 0x02
struct selinfo sel;
struct cpu_info *ci;
};
static struct intrhand *xenevt_ih;
static evtchn_port_t xenevt_ev;
static struct xenevt_d *devevent[NR_EVENT_CHANNELS];
static void *devevent_sih;
static kmutex_t devevent_lock;
static STAILQ_HEAD(, xenevt_d) devevent_pending;
static void xenevt_record(struct xenevt_d *, evtchn_port_t);
static void xenevt_free(struct xenevt_d *);
long xenevt_ev1;
long xenevt_ev2[NR_EVENT_CHANNELS];
static int xenevt_processevt(void *);
static evtchn_port_t xenevt_alloc_event(void)
{
evtchn_op_t op;
op.cmd = EVTCHNOP_alloc_unbound;
op.u.alloc_unbound.dom = DOMID_SELF;
op.u.alloc_unbound.remote_dom = DOMID_SELF;
if (HYPERVISOR_event_channel_op(&op) != 0)
panic("%s: Failed to allocate loopback event\n", __func__);
return op.u.alloc_unbound.port;
}
void
xenevtattach(int n)
{
int level = IPL_HIGH;
if (!xendomain_is_privileged())
return;
#ifndef XENPV
if (vm_guest != VM_GUEST_XENPVH)
return;
#endif
mutex_init(&devevent_lock, MUTEX_DEFAULT, IPL_HIGH);
STAILQ_INIT(&devevent_pending);
devevent_sih = softint_establish(SOFTINT_SERIAL,
(void (*)(void *))xenevt_notify, NULL);
memset(devevent, 0, sizeof(devevent));
xenevt_ev1 = 0;
memset(xenevt_ev2, 0, sizeof(xenevt_ev2));
xenevt_ev = xenevt_alloc_event();
xenevt_ih = event_set_handler(xenevt_ev, xenevt_processevt, NULL,
level, NULL, "xenevt", true, &cpu_info_primary);
KASSERT(xenevt_ih != NULL);
}
void
xenevt_setipending(int l1, int l2)
{
KASSERT(curcpu() == xenevt_ih->ih_cpu);
KASSERT(xenevt_ih->ih_cpu->ci_ilevel >= IPL_HIGH);
atomic_or_ulong(&xenevt_ev1, 1UL << l1);
atomic_or_ulong(&xenevt_ev2[l1], 1UL << l2);
atomic_or_64(&xenevt_ih->ih_cpu->ci_istate, (1ULL << SIR_XENIPL_HIGH));
atomic_add_int(&xenevt_ih->ih_pending, 1);
evtsource[xenevt_ev]->ev_evcnt.ev_count++;
}
static int
xenevt_processevt(void *v)
{
long l1, l2;
int l1i, l2i;
int port;
l1 = xen_atomic_xchg(&xenevt_ev1, 0);
while ((l1i = xen_ffs(l1)) != 0) {
l1i--;
l1 &= ~(1UL << l1i);
l2 = xen_atomic_xchg(&xenevt_ev2[l1i], 0);
while ((l2i = xen_ffs(l2)) != 0) {
l2i--;
l2 &= ~(1UL << l2i);
port = (l1i << LONG_SHIFT) + l2i;
xenevt_event(port);
}
}
return 0;
}
void
xenevt_event(int port)
{
struct xenevt_d *d;
mutex_enter(&devevent_lock);
d = devevent[port];
if (d != NULL) {
xenevt_record(d, port);
if (d->pending == false) {
STAILQ_INSERT_TAIL(&devevent_pending, d, pendingq);
d->pending = true;
mutex_exit(&devevent_lock);
softint_schedule(devevent_sih);
return;
}
}
mutex_exit(&devevent_lock);
}
void
xenevt_notify(void)
{
struct xenevt_d *d;
for (;;) {
mutex_enter(&devevent_lock);
d = STAILQ_FIRST(&devevent_pending);
if (d == NULL) {
mutex_exit(&devevent_lock);
break;
}
STAILQ_REMOVE_HEAD(&devevent_pending, pendingq);
d->pending = false;
mutex_enter(&d->lock);
if (d->flags & XENEVT_F_FREE) {
xenevt_free(d);
} else {
mutex_exit(&devevent_lock);
selnotify(&d->sel, 0, 1);
cv_broadcast(&d->cv);
mutex_exit(&d->lock);
}
}
}
static void
xenevt_record(struct xenevt_d *d, evtchn_port_t port)
{
mutex_enter(&d->lock);
if (d->ring_read ==
((d->ring_write + 1) & XENEVT_RING_MASK)) {
d->flags |= XENEVT_F_OVERFLOW;
printf("xenevt_event: ring overflow port %d\n", port);
} else {
d->ring[d->ring_write] = port;
d->ring_write = (d->ring_write + 1) & XENEVT_RING_MASK;
}
mutex_exit(&d->lock);
}
int
xenevtopen(dev_t dev, int flags, int mode, struct lwp *l)
{
struct xenevt_d *d;
struct file *fp;
int fd, error;
switch(minor(dev)) {
case DEV_EVT:
if ((error = fd_allocfile(&fp, &fd)) != 0)
return error;
d = kmem_zalloc(sizeof(*d), KM_SLEEP);
d->ci = xenevt_ih->ih_cpu;
mutex_init(&d->lock, MUTEX_DEFAULT, IPL_HIGH);
cv_init(&d->cv, "xenevt");
selinit(&d->sel);
return fd_clone(fp, fd, flags, &xenevt_fileops, d);
case DEV_XSD:
return (0);
default:
break;
}
return ENODEV;
}
int
xenevtread(dev_t dev, struct uio *uio, int flags)
{
#define LD_STRLEN 21
if (minor(dev) == DEV_XSD) {
char strbuf[LD_STRLEN], *bf;
int off, error;
size_t len;
off = (int)uio->uio_offset;
if (off < 0)
return EINVAL;
len = snprintf(strbuf, sizeof(strbuf), "%ld\n",
xen_start_info.store_mfn);
if (off >= len) {
bf = strbuf;
len = 0;
} else {
bf = &strbuf[off];
len -= off;
}
error = uiomove(bf, len, uio);
return error;
}
return ENODEV;
}
paddr_t
xenevtmmap(dev_t dev, off_t off, int prot)
{
if (minor(dev) == DEV_XSD) {
if (off != 0)
return -1;
#ifdef XENPV
return x86_btop(
xpmap_mtop((paddr_t)xen_start_info.store_mfn << PAGE_SHIFT));
#else
return x86_btop(
(paddr_t)xen_start_info.store_mfn << PAGE_SHIFT);
#endif
}
return -1;
}
static void
xenevt_free(struct xenevt_d *d)
{
int i;
KASSERT(mutex_owned(&devevent_lock));
KASSERT(mutex_owned(&d->lock));
for (i = 0; i < NR_EVENT_CHANNELS; i++ ) {
if (devevent[i] == d) {
evtchn_op_t op = { .cmd = 0 };
int error;
hypervisor_mask_event(i);
xen_atomic_clear_bit(&d->ci->ci_evtmask[0], i);
devevent[i] = NULL;
op.cmd = EVTCHNOP_close;
op.u.close.port = i;
if ((error = HYPERVISOR_event_channel_op(&op))) {
printf("xenevt_fclose: error %d from "
"hypervisor\n", -error);
}
}
}
mutex_exit(&d->lock);
mutex_exit(&devevent_lock);
seldestroy(&d->sel);
cv_destroy(&d->cv);
mutex_destroy(&d->lock);
kmem_free(d, sizeof(*d));
}
static int
xenevt_fclose(struct file *fp)
{
struct xenevt_d *d = fp->f_data;
mutex_enter(&devevent_lock);
mutex_enter(&d->lock);
if (d->pending) {
d->flags |= XENEVT_F_FREE;
mutex_exit(&d->lock);
mutex_exit(&devevent_lock);
} else {
xenevt_free(d);
}
fp->f_data = NULL;
return (0);
}
static int
xenevt_fread(struct file *fp, off_t *offp, struct uio *uio,
kauth_cred_t cred, int flags)
{
struct xenevt_d *d = fp->f_data;
int error, ring_read, ring_write;
size_t len, uio_len;
error = 0;
mutex_enter(&d->lock);
while (error == 0) {
ring_read = d->ring_read;
ring_write = d->ring_write;
if (ring_read != ring_write) {
break;
}
if (d->flags & XENEVT_F_OVERFLOW) {
break;
}
if ((fp->f_flag & FNONBLOCK) == 0) {
error = cv_wait_sig(&d->cv, &d->lock);
} else {
error = EAGAIN;
}
}
if (error == 0 && (d->flags & XENEVT_F_OVERFLOW)) {
error = EFBIG;
}
mutex_exit(&d->lock);
if (error) {
return error;
}
uio_len = uio->uio_resid / BYTES_PER_PORT;
if (ring_read <= ring_write)
len = ring_write - ring_read;
else
len = XENEVT_RING_SIZE - ring_read;
if (len > uio_len)
len = uio_len;
error = uiomove(&d->ring[ring_read], len * BYTES_PER_PORT, uio);
if (error)
return error;
ring_read = (ring_read + len) & XENEVT_RING_MASK;
uio_len = uio->uio_resid / BYTES_PER_PORT;
if (uio_len == 0)
goto done;
len = ring_write - ring_read;
if (len > uio_len)
len = uio_len;
error = uiomove(&d->ring[ring_read], len * BYTES_PER_PORT, uio);
if (error)
return error;
ring_read = (ring_read + len) & XENEVT_RING_MASK;
done:
mutex_enter(&d->lock);
d->ring_read = ring_read;
mutex_exit(&d->lock);
return 0;
}
static int
xenevt_fwrite(struct file *fp, off_t *offp, struct uio *uio,
kauth_cred_t cred, int flags)
{
struct xenevt_d *d = fp->f_data;
uint16_t *chans;
int i, nentries, error;
if (uio->uio_resid == 0)
return (0);
nentries = uio->uio_resid / sizeof(uint16_t);
if (nentries >= NR_EVENT_CHANNELS)
return EMSGSIZE;
chans = kmem_alloc(nentries * sizeof(uint16_t), KM_SLEEP);
error = uiomove(chans, uio->uio_resid, uio);
if (error)
goto out;
mutex_enter(&devevent_lock);
for (i = 0; i < nentries; i++) {
if (chans[i] < NR_EVENT_CHANNELS &&
devevent[chans[i]] == d) {
hypervisor_unmask_event(chans[i]);
}
}
mutex_exit(&devevent_lock);
out:
kmem_free(chans, nentries * sizeof(uint16_t));
return 0;
}
static int
xenevt_fioctl(struct file *fp, u_long cmd, void *addr)
{
struct xenevt_d *d = fp->f_data;
evtchn_op_t op = { .cmd = 0 };
int error;
switch(cmd) {
case EVTCHN_RESET:
case IOCTL_EVTCHN_RESET:
mutex_enter(&d->lock);
d->ring_read = d->ring_write = 0;
d->flags = 0;
mutex_exit(&d->lock);
break;
case IOCTL_EVTCHN_BIND_VIRQ:
{
struct ioctl_evtchn_bind_virq *bind_virq = addr;
op.cmd = EVTCHNOP_bind_virq;
op.u.bind_virq.virq = bind_virq->virq;
op.u.bind_virq.vcpu = 0;
if ((error = HYPERVISOR_event_channel_op(&op))) {
printf("IOCTL_EVTCHN_BIND_VIRQ failed: virq %d error %d\n", bind_virq->virq, error);
return -error;
}
bind_virq->port = op.u.bind_virq.port;
mutex_enter(&devevent_lock);
KASSERT(devevent[bind_virq->port] == NULL);
devevent[bind_virq->port] = d;
mutex_exit(&devevent_lock);
xen_atomic_set_bit(&d->ci->ci_evtmask[0], bind_virq->port);
hypervisor_unmask_event(bind_virq->port);
break;
}
case IOCTL_EVTCHN_BIND_INTERDOMAIN:
{
struct ioctl_evtchn_bind_interdomain *bind_intd = addr;
op.cmd = EVTCHNOP_bind_interdomain;
op.u.bind_interdomain.remote_dom = bind_intd->remote_domain;
op.u.bind_interdomain.remote_port = bind_intd->remote_port;
if ((error = HYPERVISOR_event_channel_op(&op)))
return -error;
bind_intd->port = op.u.bind_interdomain.local_port;
mutex_enter(&devevent_lock);
KASSERT(devevent[bind_intd->port] == NULL);
devevent[bind_intd->port] = d;
mutex_exit(&devevent_lock);
xen_atomic_set_bit(&d->ci->ci_evtmask[0], bind_intd->port);
hypervisor_unmask_event(bind_intd->port);
break;
}
case IOCTL_EVTCHN_BIND_UNBOUND_PORT:
{
struct ioctl_evtchn_bind_unbound_port *bind_unbound = addr;
op.cmd = EVTCHNOP_alloc_unbound;
op.u.alloc_unbound.dom = DOMID_SELF;
op.u.alloc_unbound.remote_dom = bind_unbound->remote_domain;
if ((error = HYPERVISOR_event_channel_op(&op)))
return -error;
bind_unbound->port = op.u.alloc_unbound.port;
mutex_enter(&devevent_lock);
KASSERT(devevent[bind_unbound->port] == NULL);
devevent[bind_unbound->port] = d;
mutex_exit(&devevent_lock);
xen_atomic_set_bit(&d->ci->ci_evtmask[0], bind_unbound->port);
hypervisor_unmask_event(bind_unbound->port);
break;
}
case IOCTL_EVTCHN_UNBIND:
{
struct ioctl_evtchn_unbind *unbind = addr;
if (unbind->port >= NR_EVENT_CHANNELS)
return EINVAL;
mutex_enter(&devevent_lock);
if (devevent[unbind->port] != d) {
mutex_exit(&devevent_lock);
return ENOTCONN;
}
devevent[unbind->port] = NULL;
mutex_exit(&devevent_lock);
hypervisor_mask_event(unbind->port);
xen_atomic_clear_bit(&d->ci->ci_evtmask[0], unbind->port);
op.cmd = EVTCHNOP_close;
op.u.close.port = unbind->port;
if ((error = HYPERVISOR_event_channel_op(&op)))
return -error;
break;
}
case IOCTL_EVTCHN_NOTIFY:
{
struct ioctl_evtchn_notify *notify = addr;
if (notify->port >= NR_EVENT_CHANNELS)
return EINVAL;
mutex_enter(&devevent_lock);
if (devevent[notify->port] != d) {
mutex_exit(&devevent_lock);
return ENOTCONN;
}
hypervisor_notify_via_evtchn(notify->port);
mutex_exit(&devevent_lock);
break;
}
case FIONBIO:
break;
default:
return EINVAL;
}
return 0;
}
static int
xenevt_fpoll(struct file *fp, int events)
{
struct xenevt_d *d = fp->f_data;
int revents = events & (POLLOUT | POLLWRNORM);
mutex_enter(&d->lock);
if (events & (POLLIN | POLLRDNORM)) {
if (d->ring_read != d->ring_write) {
revents |= events & (POLLIN | POLLRDNORM);
} else {
selrecord(curlwp, &d->sel);
}
}
mutex_exit(&d->lock);
return (revents);
}