From owner-dev-commits-src-main@freebsd.org Tue Jun 8 11:03:36 2021 Return-Path: Delivered-To: dev-commits-src-main@mailman.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mailman.nyi.freebsd.org (Postfix) with ESMTP id 23FCB65D3DB; Tue, 8 Jun 2021 11:03:36 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R3" (verified OK)) by mx1.freebsd.org (Postfix) with ESMTPS id 4FznRD0KtQz4T26; Tue, 8 Jun 2021 11:03:36 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256) (Client did not present a certificate) by mxrelay.nyi.freebsd.org (Postfix) with ESMTPS id E7941279E7; Tue, 8 Jun 2021 11:03:35 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from gitrepo.freebsd.org ([127.0.1.44]) by gitrepo.freebsd.org (8.16.1/8.16.1) with ESMTP id 158B3ZB5075468; Tue, 8 Jun 2021 11:03:35 GMT (envelope-from git@gitrepo.freebsd.org) Received: (from git@localhost) by gitrepo.freebsd.org (8.16.1/8.16.1/Submit) id 158B3ZuE075467; Tue, 8 Jun 2021 11:03:35 GMT (envelope-from git) Date: Tue, 8 Jun 2021 11:03:35 GMT Message-Id: <202106081103.158B3ZuE075467@gitrepo.freebsd.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Peter Grehan Subject: git: 054accac71e0 - main - Add a virtio-input device emulation. MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: grehan X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 054accac71e0944ab588c3ab052bea6947df7885 Auto-Submitted: auto-generated X-BeenThere: dev-commits-src-main@freebsd.org X-Mailman-Version: 2.1.34 Precedence: list List-Id: Commit messages for the main branch of the src repository List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Tue, 08 Jun 2021 11:03:36 -0000 The branch main has been updated by grehan: URL: https://cgit.FreeBSD.org/src/commit/?id=054accac71e0944ab588c3ab052bea6947df7885 commit 054accac71e0944ab588c3ab052bea6947df7885 Author: Corvin Köhne AuthorDate: 2021-06-08 08:56:43 +0000 Commit: Peter Grehan CommitDate: 2021-06-08 09:05:09 +0000 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 ,virtio-input,/dev/input/eventX Reviewed by: jhb (bhyve), grehan Obtained from: Corvin Köhne MFC after: 3 weeks Relnotes: yes Differential Revision: https://reviews.freebsd.org/D30020 --- usr.sbin/bhyve/Makefile | 1 + usr.sbin/bhyve/bhyve.8 | 10 + usr.sbin/bhyve/bhyve_config.5 | 11 + usr.sbin/bhyve/pci_virtio_input.c | 782 ++++++++++++++++++++++++++++++++++++++ usr.sbin/bhyve/virtio.c | 10 +- usr.sbin/bhyve/virtio.h | 16 + 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 +__FBSDID("$FreeBSD$"); + +#include +#ifndef WITHOUT_CAPSICUM +#include + +#include +#endif +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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