aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCorvin Köhne <C.Koehne@beckhoff.com>2021-06-08 08:56:43 +0000
committerPeter Grehan <grehan@FreeBSD.org>2021-06-08 09:05:09 +0000
commit054accac71e0944ab588c3ab052bea6947df7885 (patch)
tree9e6ed356eb4da10232dd624e9ff2f1a6ce3d4650
parent09307dbfb888a98232096c751a96ecb3344aa77c (diff)
downloadsrc-054accac71e0.tar.gz
src-054accac71e0.zip
Add a virtio-input device emulation.
This will be used to inject keyboard/mouse input events into a guest. The command line syntax is: -s <slot>,virtio-input,/dev/input/eventX Reviewed by: jhb (bhyve), grehan Obtained from: Corvin Köhne <C.Koehne@beckhoff.com> MFC after: 3 weeks Relnotes: yes Differential Revision: https://reviews.freebsd.org/D30020
-rw-r--r--usr.sbin/bhyve/Makefile1
-rw-r--r--usr.sbin/bhyve/bhyve.810
-rw-r--r--usr.sbin/bhyve/bhyve_config.511
-rw-r--r--usr.sbin/bhyve/pci_virtio_input.c782
-rw-r--r--usr.sbin/bhyve/virtio.c10
-rw-r--r--usr.sbin/bhyve/virtio.h16
6 files changed, 828 insertions, 2 deletions
diff --git a/usr.sbin/bhyve/Makefile b/usr.sbin/bhyve/Makefile
index e35d528ab605..772c0988090e 100644
--- a/usr.sbin/bhyve/Makefile
+++ b/usr.sbin/bhyve/Makefile
@@ -50,6 +50,7 @@ SRCS= \
pci_virtio_9p.c \
pci_virtio_block.c \
pci_virtio_console.c \
+ pci_virtio_input.c \
pci_virtio_net.c \
pci_virtio_rnd.c \
pci_virtio_scsi.c \
diff --git a/usr.sbin/bhyve/bhyve.8 b/usr.sbin/bhyve/bhyve.8
index 79b6d9bfb240..fcf47ec65513 100644
--- a/usr.sbin/bhyve/bhyve.8
+++ b/usr.sbin/bhyve/bhyve.8
@@ -306,6 +306,8 @@ Virtio RNG interface.
Virtio console interface, which exposes multiple ports
to the guest in the form of simple char devices for simple IO
between the guest and host userspaces.
+.It Cm virtio-input
+Virtio input interface.
.It Cm ahci
AHCI controller attached to arbitrary devices.
.It Cm ahci-cd
@@ -539,6 +541,14 @@ resize at present.
Emergency write is advertised, but no-op at present.
.El
.Pp
+Virtio input device backends:
+.Bl -tag -width 10n
+.It Ar /dev/input/eventX
+Send input events of
+.Ar /dev/input/eventX
+to guest by VirtIO Input Interface.
+.El
+.Pp
Framebuffer devices backends:
.Bl -bullet
.Sm off
diff --git a/usr.sbin/bhyve/bhyve_config.5 b/usr.sbin/bhyve/bhyve_config.5
index 1a77b1bbacb4..a2221d5bc4bf 100644
--- a/usr.sbin/bhyve/bhyve_config.5
+++ b/usr.sbin/bhyve/bhyve_config.5
@@ -221,6 +221,8 @@ VirtIO 9p (VirtFS) interface.
VirtIO block storage interface.
.It Li virtio-console
VirtIO console interface.
+.It Li virtio-input
+VirtIO input interface.
.It Li virtio-net
VirtIO network interface.
.It Li virtio-rnd
@@ -528,6 +530,15 @@ The name of the port exposed to the guest.
.It Va path Ta path Ta Ta
The path of a UNIX domain socket providing the host connection for the port.
.El
+.Ss VirtIO Input Interface Settings
+Each VirtIO Input device contains one input event device.
+All input events of the input event device are send to the guest by VirtIO Input interface.
+VirtIO Input Interfaces support the following variables:
+.Bl -column "Name" "Format" "Default"
+.It Sy Name Ta Sy Format Ta Sy Default Ta Sy Description
+.It Va path Ta path Ta Ta
+The path of the input event device exposed to the guest
+.El
.Ss VirtIO Network Interface Settings
In addition to the network backend settings,
VirtIO network interfaces support the following variables:
diff --git a/usr.sbin/bhyve/pci_virtio_input.c b/usr.sbin/bhyve/pci_virtio_input.c
new file mode 100644
index 000000000000..4517333b1603
--- /dev/null
+++ b/usr.sbin/bhyve/pci_virtio_input.c
@@ -0,0 +1,782 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2021 Beckhoff Automation GmbH & Co. KG
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer
+ * in this position and unchanged.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * virtio input device emulation.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#ifndef WITHOUT_CAPSICUM
+#include <sys/capsicum.h>
+
+#include <capsicum_helpers.h>
+#endif
+#include <sys/ioctl.h>
+#include <sys/linker_set.h>
+#include <sys/uio.h>
+
+#include <dev/evdev/input.h>
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sysexits.h>
+#include <unistd.h>
+
+#include "bhyverun.h"
+#include "config.h"
+#include "debug.h"
+#include "mevent.h"
+#include "pci_emul.h"
+#include "virtio.h"
+
+#define VTINPUT_RINGSZ 64
+
+#define VTINPUT_MAX_PKT_LEN 10
+
+/*
+ * Queue definitions.
+ */
+#define VTINPUT_EVENTQ 0
+#define VTINPUT_STATUSQ 1
+
+#define VTINPUT_MAXQ 2
+
+static int pci_vtinput_debug;
+#define DPRINTF(params) \
+ if (pci_vtinput_debug) \
+ PRINTLN params
+#define WPRINTF(params) PRINTLN params
+
+enum vtinput_config_select {
+ VTINPUT_CFG_UNSET = 0x00,
+ VTINPUT_CFG_ID_NAME = 0x01,
+ VTINPUT_CFG_ID_SERIAL = 0x02,
+ VTINPUT_CFG_ID_DEVIDS = 0x03,
+ VTINPUT_CFG_PROP_BITS = 0x10,
+ VTINPUT_CFG_EV_BITS = 0x11,
+ VTINPUT_CFG_ABS_INFO = 0x12
+};
+
+struct vtinput_absinfo {
+ uint32_t min;
+ uint32_t max;
+ uint32_t fuzz;
+ uint32_t flat;
+ uint32_t res;
+} __packed;
+
+struct vtinput_devids {
+ uint16_t bustype;
+ uint16_t vendor;
+ uint16_t product;
+ uint16_t version;
+} __packed;
+
+struct vtinput_config {
+ uint8_t select;
+ uint8_t subsel;
+ uint8_t size;
+ uint8_t reserved[5];
+ union {
+ char string[128];
+ uint8_t bitmap[128];
+ struct vtinput_absinfo abs;
+ struct vtinput_devids ids;
+ } u;
+} __packed;
+
+struct vtinput_event {
+ uint16_t type;
+ uint16_t code;
+ uint32_t value;
+} __packed;
+
+struct vtinput_event_elem {
+ struct vtinput_event event;
+ struct iovec iov;
+ uint16_t idx;
+};
+
+struct vtinput_eventqueue {
+ struct vtinput_event_elem *events;
+ uint32_t size;
+ uint32_t idx;
+};
+
+/*
+ * Per-device softc
+ */
+struct pci_vtinput_softc {
+ struct virtio_softc vsc_vs;
+ struct vqueue_info vsc_queues[VTINPUT_MAXQ];
+ pthread_mutex_t vsc_mtx;
+ const char *vsc_evdev;
+ int vsc_fd;
+ struct vtinput_config vsc_config;
+ int vsc_config_valid;
+ struct mevent *vsc_evp;
+ struct vtinput_eventqueue vsc_eventqueue;
+};
+
+static void pci_vtinput_reset(void *);
+static int pci_vtinput_cfgread(void *, int, int, uint32_t *);
+static int pci_vtinput_cfgwrite(void *, int, int, uint32_t);
+
+static struct virtio_consts vtinput_vi_consts = {
+ "vtinput", /* our name */
+ VTINPUT_MAXQ, /* we support 1 virtqueue */
+ sizeof(struct vtinput_config), /* config reg size */
+ pci_vtinput_reset, /* reset */
+ NULL, /* device-wide qnotify -- not used */
+ pci_vtinput_cfgread, /* read virtio config */
+ pci_vtinput_cfgwrite, /* write virtio config */
+ NULL, /* apply negotiated features */
+ 0, /* our capabilities */
+};
+
+static void
+pci_vtinput_reset(void *vsc)
+{
+ struct pci_vtinput_softc *sc = vsc;
+
+ DPRINTF(("%s: device reset requested", __func__));
+ vi_reset_dev(&sc->vsc_vs);
+}
+
+static void
+pci_vtinput_notify_eventq(void *vsc, struct vqueue_info *vq)
+{
+ DPRINTF(("%s", __func__));
+}
+
+static void
+pci_vtinput_notify_statusq(void *vsc, struct vqueue_info *vq)
+{
+ struct pci_vtinput_softc *sc = vsc;
+
+ while (vq_has_descs(vq)) {
+ /* get descriptor chain */
+ struct iovec iov;
+ struct vi_req req;
+ const int n = vq_getchain(vq, &iov, 1, &req);
+ if (n <= 0) {
+ WPRINTF(("%s: invalid descriptor: %d", __func__, n));
+ return;
+ }
+
+ /* get event */
+ struct vtinput_event event;
+ memcpy(&event, iov.iov_base, sizeof(event));
+
+ /*
+ * on multi touch devices:
+ * - host send EV_MSC to guest
+ * - guest sends EV_MSC back to host
+ * - host writes EV_MSC to evdev
+ * - evdev saves EV_MSC in it's event buffer
+ * - host receives an extra EV_MSC by reading the evdev event
+ * buffer
+ * - frames become larger and larger
+ * avoid endless loops by ignoring EV_MSC
+ */
+ if (event.type == EV_MSC) {
+ vq_relchain(vq, req.idx, sizeof(event));
+ continue;
+ }
+
+ /* send event to evdev */
+ struct input_event host_event;
+ host_event.type = event.type;
+ host_event.code = event.code;
+ host_event.value = event.value;
+ if (gettimeofday(&host_event.time, NULL) != 0) {
+ WPRINTF(("%s: failed gettimeofday", __func__));
+ }
+ if (write(sc->vsc_fd, &host_event, sizeof(host_event)) == -1) {
+ WPRINTF(("%s: failed to write host_event", __func__));
+ }
+
+ vq_relchain(vq, req.idx, sizeof(event));
+ }
+ vq_endchains(vq, 1);
+}
+
+static int
+pci_vtinput_get_bitmap(struct pci_vtinput_softc *sc, int cmd, int count)
+{
+ if (count <= 0 || !sc) {
+ return (-1);
+ }
+
+ /* query bitmap */
+ memset(sc->vsc_config.u.bitmap, 0, sizeof(sc->vsc_config.u.bitmap));
+ if (ioctl(sc->vsc_fd, cmd, sc->vsc_config.u.bitmap) < 0) {
+ return (-1);
+ }
+
+ /* get number of set bytes in bitmap */
+ for (int i = count - 1; i >= 0; i--) {
+ if (sc->vsc_config.u.bitmap[i]) {
+ return i + 1;
+ }
+ }
+
+ return (-1);
+}
+
+static int
+pci_vtinput_read_config_id_name(struct pci_vtinput_softc *sc)
+{
+ char name[128];
+ if (ioctl(sc->vsc_fd, EVIOCGNAME(sizeof(name) - 1), name) < 0) {
+ return (1);
+ }
+
+ memcpy(sc->vsc_config.u.string, name, sizeof(name));
+ sc->vsc_config.size = strnlen(name, sizeof(name));
+
+ return (0);
+}
+
+static int
+pci_vtinput_read_config_id_serial(struct pci_vtinput_softc *sc)
+{
+ /* serial isn't supported */
+ sc->vsc_config.size = 0;
+
+ return (0);
+}
+
+static int
+pci_vtinput_read_config_id_devids(struct pci_vtinput_softc *sc)
+{
+ struct input_id devids;
+ if (ioctl(sc->vsc_fd, EVIOCGID, &devids)) {
+ return (1);
+ }
+
+ sc->vsc_config.u.ids.bustype = devids.bustype;
+ sc->vsc_config.u.ids.vendor = devids.vendor;
+ sc->vsc_config.u.ids.product = devids.product;
+ sc->vsc_config.u.ids.version = devids.version;
+ sc->vsc_config.size = sizeof(struct vtinput_devids);
+
+ return (0);
+}
+
+static int
+pci_vtinput_read_config_prop_bits(struct pci_vtinput_softc *sc)
+{
+ /*
+ * Evdev bitmap countains 1 bit per count. Additionally evdev bitmaps
+ * are arrays of longs instead of chars. Calculate how many longs are
+ * required for evdev bitmap. Multiply that with sizeof(long) to get the
+ * number of elements.
+ */
+ const int count = howmany(INPUT_PROP_CNT, sizeof(long) * 8) *
+ sizeof(long);
+ const unsigned int cmd = EVIOCGPROP(count);
+ const int size = pci_vtinput_get_bitmap(sc, cmd, count);
+ if (size <= 0) {
+ return (1);
+ }
+
+ sc->vsc_config.size = size;
+
+ return (0);
+}
+
+static int
+pci_vtinput_read_config_ev_bits(struct pci_vtinput_softc *sc, uint8_t type)
+{
+ int count;
+
+ switch (type) {
+ case EV_KEY:
+ count = KEY_CNT;
+ break;
+ case EV_REL:
+ count = REL_CNT;
+ break;
+ case EV_ABS:
+ count = ABS_CNT;
+ break;
+ case EV_MSC:
+ count = MSC_CNT;
+ break;
+ case EV_SW:
+ count = SW_CNT;
+ break;
+ case EV_LED:
+ count = LED_CNT;
+ break;
+ default:
+ return (1);
+ }
+
+ /*
+ * Evdev bitmap countains 1 bit per count. Additionally evdev bitmaps
+ * are arrays of longs instead of chars. Calculate how many longs are
+ * required for evdev bitmap. Multiply that with sizeof(long) to get the
+ * number of elements.
+ */
+ count = howmany(count, sizeof(long) * 8) * sizeof(long);
+ const unsigned int cmd = EVIOCGBIT(sc->vsc_config.subsel, count);
+ const int size = pci_vtinput_get_bitmap(sc, cmd, count);
+ if (size <= 0) {
+ return (1);
+ }
+
+ sc->vsc_config.size = size;
+
+ return (0);
+}
+
+static int
+pci_vtinput_read_config_abs_info(struct pci_vtinput_softc *sc)
+{
+ /* check if evdev has EV_ABS */
+ if (!pci_vtinput_read_config_ev_bits(sc, EV_ABS)) {
+ return (1);
+ }
+
+ /* get abs information */
+ struct input_absinfo abs;
+ if (ioctl(sc->vsc_fd, EVIOCGABS(sc->vsc_config.subsel), &abs) < 0) {
+ return (1);
+ }
+
+ /* save abs information */
+ sc->vsc_config.u.abs.min = abs.minimum;
+ sc->vsc_config.u.abs.max = abs.maximum;
+ sc->vsc_config.u.abs.fuzz = abs.fuzz;
+ sc->vsc_config.u.abs.flat = abs.flat;
+ sc->vsc_config.u.abs.res = abs.resolution;
+ sc->vsc_config.size = sizeof(struct vtinput_absinfo);
+
+ return (0);
+}
+
+static int
+pci_vtinput_read_config(struct pci_vtinput_softc *sc)
+{
+ switch (sc->vsc_config.select) {
+ case VTINPUT_CFG_UNSET:
+ return (0);
+ case VTINPUT_CFG_ID_NAME:
+ return pci_vtinput_read_config_id_name(sc);
+ case VTINPUT_CFG_ID_SERIAL:
+ return pci_vtinput_read_config_id_serial(sc);
+ case VTINPUT_CFG_ID_DEVIDS:
+ return pci_vtinput_read_config_id_devids(sc);
+ case VTINPUT_CFG_PROP_BITS:
+ return pci_vtinput_read_config_prop_bits(sc);
+ case VTINPUT_CFG_EV_BITS:
+ return pci_vtinput_read_config_ev_bits(
+ sc, sc->vsc_config.subsel);
+ case VTINPUT_CFG_ABS_INFO:
+ return pci_vtinput_read_config_abs_info(sc);
+ default:
+ return (1);
+ }
+}
+
+static int
+pci_vtinput_cfgread(void *vsc, int offset, int size, uint32_t *retval)
+{
+ struct pci_vtinput_softc *sc = vsc;
+
+ /* check for valid offset and size */
+ if (offset + size > sizeof(struct vtinput_config)) {
+ WPRINTF(("%s: read to invalid offset/size %d/%d", __func__,
+ offset, size));
+ memset(retval, 0, size);
+ return (0);
+ }
+
+ /* read new config values, if select and subsel changed. */
+ if (!sc->vsc_config_valid) {
+ if (pci_vtinput_read_config(sc) != 0) {
+ DPRINTF(("%s: could not read config %d/%d", __func__,
+ sc->vsc_config.select, sc->vsc_config.subsel));
+ memset(retval, 0, size);
+ return (0);
+ }
+ sc->vsc_config_valid = 1;
+ }
+
+ uint8_t *ptr = (uint8_t *)&sc->vsc_config;
+ memcpy(retval, ptr + offset, size);
+
+ return (0);
+}
+
+static int
+pci_vtinput_cfgwrite(void *vsc, int offset, int size, uint32_t value)
+{
+ struct pci_vtinput_softc *sc = vsc;
+
+ /* guest can only write to select and subsel fields */
+ if (offset + size > 2) {
+ WPRINTF(("%s: write to readonly reg %d", __func__, offset));
+ return (1);
+ }
+
+ /* copy value into config */
+ uint8_t *ptr = (uint8_t *)&sc->vsc_config;
+ memcpy(ptr + offset, &value, size);
+
+ /* select/subsel changed, query new config on next cfgread */
+ sc->vsc_config_valid = 0;
+
+ return (0);
+}
+
+static int
+vtinput_eventqueue_add_event(
+ struct vtinput_eventqueue *queue, struct input_event *e)
+{
+ /* check if queue is full */
+ if (queue->idx >= queue->size) {
+ /* alloc new elements for queue */
+ const uint32_t newSize = queue->idx;
+ const void *newPtr = realloc(queue->events,
+ queue->size * sizeof(struct vtinput_event_elem));
+ if (newPtr == NULL) {
+ WPRINTF(("%s: realloc memory for eventqueue failed!",
+ __func__));
+ return (1);
+ }
+ /* save new size and eventqueue */
+ queue->events = (struct vtinput_event_elem *)newPtr;
+ queue->size = newSize;
+ }
+
+ /* save event */
+ struct vtinput_event *event = &queue->events[queue->idx].event;
+ event->type = e->type;
+ event->code = e->code;
+ event->value = e->value;
+ queue->idx++;
+
+ return (0);
+}
+
+static void
+vtinput_eventqueue_clear(struct vtinput_eventqueue *queue)
+{
+ /* just reset index to clear queue */
+ queue->idx = 0;
+}
+
+static void
+vtinput_eventqueue_send_events(
+ struct vtinput_eventqueue *queue, struct vqueue_info *vq)
+{
+ /*
+ * First iteration through eventqueue:
+ * Get descriptor chains.
+ */
+ for (uint32_t i = 0; i < queue->idx; ++i) {
+ /* get descriptor */
+ if (!vq_has_descs(vq)) {
+ /*
+ * We don't have enough descriptors for all events.
+ * Return chains back to guest.
+ */
+ vq_retchains(vq, i);
+ WPRINTF((
+ "%s: not enough available descriptors, dropping %d events",
+ __func__, queue->idx));
+ goto done;
+ }
+
+ /* get descriptor chain */
+ struct iovec iov;
+ struct vi_req req;
+ const int n = vq_getchain(vq, &iov, 1, &req);
+ if (n <= 0) {
+ WPRINTF(("%s: invalid descriptor: %d", __func__, n));
+ return;
+ }
+ if (n != 1) {
+ WPRINTF(
+ ("%s: invalid number of descriptors in chain: %d",
+ __func__, n));
+ /* release invalid chain */
+ vq_relchain(vq, req.idx, 0);
+ return;
+ }
+ if (iov.iov_len < sizeof(struct vtinput_event)) {
+ WPRINTF(("%s: invalid descriptor length: %lu", __func__,
+ iov.iov_len));
+ /* release invalid chain */
+ vq_relchain(vq, req.idx, 0);
+ return;
+ }
+
+ /* save descriptor */
+ queue->events[i].iov = iov;
+ queue->events[i].idx = req.idx;
+ }
+
+ /*
+ * Second iteration through eventqueue:
+ * Send events to guest by releasing chains
+ */
+ for (uint32_t i = 0; i < queue->idx; ++i) {
+ struct vtinput_event_elem event = queue->events[i];
+ memcpy(event.iov.iov_base, &event.event,
+ sizeof(struct vtinput_event));
+ vq_relchain(vq, event.idx, sizeof(struct vtinput_event));
+ }
+done:
+ /* clear queue and send interrupt to guest */
+ vtinput_eventqueue_clear(queue);
+ vq_endchains(vq, 1);
+
+ return;
+}
+
+static int
+vtinput_read_event_from_host(int fd, struct input_event *event)
+{
+ const int len = read(fd, event, sizeof(struct input_event));
+ if (len != sizeof(struct input_event)) {
+ if (len == -1 && errno != EAGAIN) {
+ WPRINTF(("%s: event read failed! len = %d, errno = %d",
+ __func__, len, errno));
+ }
+
+ /* host doesn't have more events for us */
+ return (1);
+ }
+
+ return (0);
+}
+
+static void
+vtinput_read_event(int fd __attribute((unused)),
+ enum ev_type t __attribute__((unused)), void *arg __attribute__((unused)))
+{
+ struct pci_vtinput_softc *sc = arg;
+
+ /* skip if driver isn't ready */
+ if (!(sc->vsc_vs.vs_status & VIRTIO_CONFIG_STATUS_DRIVER_OK))
+ return;
+
+ /* read all events from host */
+ struct input_event event;
+ while (vtinput_read_event_from_host(sc->vsc_fd, &event) == 0) {
+ /* add events to our queue */
+ vtinput_eventqueue_add_event(&sc->vsc_eventqueue, &event);
+
+ /* only send events to guest on EV_SYN or SYN_REPORT */
+ if (event.type != EV_SYN || event.type != SYN_REPORT) {
+ continue;
+ }
+
+ /* send host events to guest */
+ vtinput_eventqueue_send_events(
+ &sc->vsc_eventqueue, &sc->vsc_queues[VTINPUT_EVENTQ]);
+ }
+}
+
+static int
+pci_vtinput_legacy_config(nvlist_t *nvl, const char *opts)
+{
+ if (opts == NULL)
+ return (-1);
+
+ /*
+ * parse opts:
+ * virtio-input,/dev/input/eventX
+ */
+ char *cp = strchr(opts, ',');
+ if (cp == NULL) {
+ set_config_value_node(nvl, "path", opts);
+ return (0);
+ }
+ char *path = strndup(opts, cp - opts);
+ set_config_value_node(nvl, "path", path);
+ free(path);
+
+ return (pci_parse_legacy_config(nvl, cp + 1));
+}
+
+static int
+pci_vtinput_init(struct vmctx *ctx, struct pci_devinst *pi, nvlist_t *nvl)
+{
+ struct pci_vtinput_softc *sc;
+
+ /*
+ * Keep it here.
+ * Else it's possible to access it uninitialized by jumping to failed.
+ */
+ pthread_mutexattr_t mtx_attr = NULL;
+
+ sc = calloc(1, sizeof(struct pci_vtinput_softc));
+
+ sc->vsc_evdev = get_config_value_node(nvl, "path");
+ if (sc->vsc_evdev == NULL) {
+ WPRINTF(("%s: missing required path config value", __func__));
+ goto failed;
+ }
+
+ /*
+ * open evdev by using non blocking I/O:
+ * read from /dev/input/eventX would block our thread otherwise
+ */
+ sc->vsc_fd = open(sc->vsc_evdev, O_RDWR | O_NONBLOCK);
+ if (sc->vsc_fd < 0) {
+ WPRINTF(("%s: failed to open %s", __func__, sc->vsc_evdev));
+ goto failed;
+ }
+
+ /* check if evdev is really a evdev */
+ int evversion;
+ int error = ioctl(sc->vsc_fd, EVIOCGVERSION, &evversion);
+ if (error < 0) {
+ WPRINTF(("%s: %s is no evdev", __func__, sc->vsc_evdev));
+ goto failed;
+ }
+
+ /* gain exclusive access to evdev */
+ error = ioctl(sc->vsc_fd, EVIOCGRAB, 1);
+ if (error < 0) {
+ WPRINTF(("%s: failed to grab %s", __func__, sc->vsc_evdev));
+ goto failed;
+ }
+
+ if (pthread_mutexattr_init(&mtx_attr)) {
+ WPRINTF(("%s: init mutexattr failed", __func__));
+ goto failed;
+ }
+ if (pthread_mutexattr_settype(&mtx_attr, PTHREAD_MUTEX_RECURSIVE)) {
+ WPRINTF(("%s: settype mutexattr failed", __func__));
+ goto failed;
+ }
+ if (pthread_mutex_init(&sc->vsc_mtx, &mtx_attr)) {
+ WPRINTF(("%s: init mutex failed", __func__));
+ goto failed;
+ }
+
+ /* init softc */
+ sc->vsc_eventqueue.idx = 0;
+ sc->vsc_eventqueue.size = VTINPUT_MAX_PKT_LEN;
+ sc->vsc_eventqueue.events = calloc(
+ sc->vsc_eventqueue.size, sizeof(struct vtinput_event_elem));
+ sc->vsc_config_valid = 0;
+ if (sc->vsc_eventqueue.events == NULL) {
+ WPRINTF(("%s: failed to alloc eventqueue", __func__));
+ goto failed;
+ }
+
+ /* register event handler */
+ sc->vsc_evp = mevent_add(sc->vsc_fd, EVF_READ, vtinput_read_event, sc);
+ if (sc->vsc_evp == NULL) {
+ WPRINTF(("%s: could not register mevent", __func__));
+ goto failed;
+ }
+
+#ifndef WITHOUT_CAPSICUM
+ cap_rights_t rights;
+ cap_rights_init(&rights, CAP_EVENT, CAP_IOCTL, CAP_READ, CAP_WRITE);
+ if (caph_rights_limit(sc->vsc_fd, &rights) == -1) {
+ errx(EX_OSERR, "Unable to apply rights for sandbox");
+ }
+#endif
+
+ /* link virtio to softc */
+ vi_softc_linkup(
+ &sc->vsc_vs, &vtinput_vi_consts, sc, pi, sc->vsc_queues);
+ sc->vsc_vs.vs_mtx = &sc->vsc_mtx;
+
+ /* init virtio queues */
+ sc->vsc_queues[VTINPUT_EVENTQ].vq_qsize = VTINPUT_RINGSZ;
+ sc->vsc_queues[VTINPUT_EVENTQ].vq_notify = pci_vtinput_notify_eventq;
+ sc->vsc_queues[VTINPUT_STATUSQ].vq_qsize = VTINPUT_RINGSZ;
+ sc->vsc_queues[VTINPUT_STATUSQ].vq_notify = pci_vtinput_notify_statusq;
+
+ /* initialize config space */
+ pci_set_cfgdata16(pi, PCIR_DEVICE, VIRTIO_DEV_INPUT);
+ pci_set_cfgdata16(pi, PCIR_VENDOR, VIRTIO_VENDOR);
+ pci_set_cfgdata8(pi, PCIR_CLASS, PCIC_INPUTDEV);
+ pci_set_cfgdata8(pi, PCIR_SUBCLASS, PCIS_INPUTDEV_OTHER);
+ pci_set_cfgdata8(pi, PCIR_REVID, VIRTIO_REV_INPUT);
+ pci_set_cfgdata16(pi, PCIR_SUBDEV_0, VIRTIO_SUBDEV_INPUT);
+ pci_set_cfgdata16(pi, PCIR_SUBVEND_0, VIRTIO_SUBVEN_INPUT);
+
+ /* add MSI-X table BAR */
+ if (vi_intr_init(&sc->vsc_vs, 1, fbsdrun_virtio_msix()))
+ goto failed;
+ /* add virtio register */
+ vi_set_io_bar(&sc->vsc_vs, 0);
+
+ return (0);
+
+failed:
+ if (sc == NULL) {
+ return (-1);
+ }
+
+ if (sc->vsc_evp)
+ mevent_delete(sc->vsc_evp);
+ if (sc->vsc_eventqueue.events)
+ free(sc->vsc_eventqueue.events);
+ if (sc->vsc_mtx)
+ pthread_mutex_destroy(&sc->vsc_mtx);
+ if (mtx_attr)
+ pthread_mutexattr_destroy(&mtx_attr);
+ if (sc->vsc_fd)
+ close(sc->vsc_fd);
+
+ free(sc);
+
+ return (-1);
+}
+
+struct pci_devemu pci_de_vinput = {
+ .pe_emu = "virtio-input",
+ .pe_init = pci_vtinput_init,
+ .pe_legacy_config = pci_vtinput_legacy_config,
+ .pe_barwrite = vi_pci_write,
+ .pe_barread = vi_pci_read,
+};
+PCI_EMUL_SET(pci_de_vinput);
diff --git a/usr.sbin/bhyve/virtio.c b/usr.sbin/bhyve/virtio.c
index 7f0485cbb826..a08964e28563 100644
--- a/usr.sbin/bhyve/virtio.c
+++ b/usr.sbin/bhyve/virtio.c
@@ -603,7 +603,10 @@ vi_pci_read(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
max = vc->vc_cfgsize ? vc->vc_cfgsize : 0x100000000;
if (newoff + size > max)
goto bad;
- error = (*vc->vc_cfgread)(DEV_SOFTC(vs), newoff, size, &value);
+ if (vc->vc_cfgread != NULL)
+ error = (*vc->vc_cfgread)(DEV_SOFTC(vs), newoff, size, &value);
+ else
+ error = 0;
if (!error)
goto done;
}
@@ -719,7 +722,10 @@ vi_pci_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
max = vc->vc_cfgsize ? vc->vc_cfgsize : 0x100000000;
if (newoff + size > max)
goto bad;
- error = (*vc->vc_cfgwrite)(DEV_SOFTC(vs), newoff, size, value);
+ if (vc->vc_cfgwrite != NULL)
+ error = (*vc->vc_cfgwrite)(DEV_SOFTC(vs), newoff, size, value);
+ else
+ error = 0;
if (!error)
goto done;
}
diff --git a/usr.sbin/bhyve/virtio.h b/usr.sbin/bhyve/virtio.h
index e03fd5f710d1..4c7b9de83125 100644
--- a/usr.sbin/bhyve/virtio.h
+++ b/usr.sbin/bhyve/virtio.h
@@ -171,6 +171,22 @@
#define VIRTIO_DEV_RANDOM 0x1005
#define VIRTIO_DEV_SCSI 0x1008
#define VIRTIO_DEV_9P 0x1009
+#define VIRTIO_DEV_INPUT 0x1052
+
+/*
+ * PCI revision IDs
+ */
+#define VIRTIO_REV_INPUT 1
+
+/*
+ * PCI subvendor IDs
+ */
+#define VIRTIO_SUBVEN_INPUT 0x108E
+
+/*
+ * PCI subdevice IDs
+ */
+#define VIRTIO_SUBDEV_INPUT 0x1100
/* From section 2.3, "Virtqueue Configuration", of the virtio specification */
static inline int