acrn-hypervisor/devicemodel/hw/pci/virtio/virtio_input.c
Geoffroy Van Cutsem 8b16be9185 Remove "All rights reserved" string headers
Many of the license and Intel copyright headers include the "All rights
reserved" string. It is not relevant in the context of the BSD-3-Clause
license that the code is released under. This patch removes those strings
throughout the code (hypervisor, devicemodel and misc).

Tracked-On: #7254
Signed-off-by: Geoffroy Van Cutsem <geoffroy.vancutsem@intel.com>
2022-04-06 13:21:02 +08:00

764 lines
17 KiB
C

/*
* Copyright (C) 2018 Intel Corporation.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
#include <sys/types.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include "dm.h"
#include "pci_core.h"
#include "virtio.h"
#include "mevent.h"
#include <linux/input.h>
static int virtio_input_debug;
#define DPRINTF(params) do { if (virtio_input_debug) pr_dbg params; } while (0)
#define WPRINTF(params) (pr_err params)
/*
* Queue definitions.
*/
#define VIRTIO_INPUT_EVENT_QUEUE 0
#define VIRTIO_INPUT_STATUS_QUEUE 1
#define VIRTIO_INPUT_MAXQ 2
/*
* Virtqueue size.
*/
#define VIRTIO_INPUT_RINGSZ 64
/*
* Default size of the buffer used to hold events between SYN
*/
#define VIRTIO_INPUT_PACKET_SIZE 10
/*
* Host capabilities
*/
#define VIRTIO_INPUT_S_HOSTCAPS (1UL << VIRTIO_F_VERSION_1)
enum virtio_input_config_select {
VIRTIO_INPUT_CFG_UNSET = 0x00,
VIRTIO_INPUT_CFG_ID_NAME = 0x01,
VIRTIO_INPUT_CFG_ID_SERIAL = 0x02,
VIRTIO_INPUT_CFG_ID_DEVIDS = 0x03,
VIRTIO_INPUT_CFG_PROP_BITS = 0x10,
VIRTIO_INPUT_CFG_EV_BITS = 0x11,
VIRTIO_INPUT_CFG_ABS_INFO = 0x12,
};
struct virtio_input_absinfo {
uint32_t min;
uint32_t max;
uint32_t fuzz;
uint32_t flat;
uint32_t res;
};
struct virtio_input_devids {
uint16_t bustype;
uint16_t vendor;
uint16_t product;
uint16_t version;
};
struct virtio_input_event {
uint16_t type;
uint16_t code;
uint32_t value;
};
/*
* Device-specific configuration registers
* To query a specific piece of configuration information FE driver sets
* "select" and "subsel" accordingly, information size is returned in "size"
* and information data is returned in union "u"
*/
struct virtio_input_config {
uint8_t select;
uint8_t subsel;
uint8_t size;
uint8_t reserved[5];
union {
char string[128];
uint8_t bitmap[128];
struct virtio_input_absinfo abs;
struct virtio_input_devids ids;
} u;
};
struct virtio_input_event_elem {
struct virtio_input_event event;
struct iovec iov;
uint16_t idx;
};
/*
* Per-device struct
*/
struct virtio_input {
struct virtio_base base;
struct virtio_vq_info queues[VIRTIO_INPUT_MAXQ];
pthread_mutex_t mtx;
struct mevent *mevp;
uint64_t features;
struct virtio_input_config cfg;
char *evdev;
char *serial;
int fd;
bool ready;
struct virtio_input_event_elem *event_queue;
uint32_t event_qsize;
uint32_t event_qindex;
};
static void virtio_input_reset(void *);
static void virtio_input_neg_features(void *, uint64_t);
static void virtio_input_set_status(void *, uint64_t);
static int virtio_input_cfgread(void *, int, int, uint32_t *);
static int virtio_input_cfgwrite(void *, int, int, uint32_t);
static bool virtio_input_get_config(struct virtio_input *, uint8_t, uint8_t,
struct virtio_input_config *);
static struct virtio_ops virtio_input_ops = {
"virtio_input", /* our name */
VIRTIO_INPUT_MAXQ, /* we support VTCON_MAXQ virtqueues */
sizeof(struct virtio_input_config), /* config reg size */
virtio_input_reset, /* reset */
NULL, /* device-wide qnotify */
virtio_input_cfgread, /* read virtio config */
virtio_input_cfgwrite, /* write virtio config */
virtio_input_neg_features, /* apply negotiated features */
virtio_input_set_status, /* called on guest set status */
};
static void
virtio_input_reset(void *vdev)
{
struct virtio_input *vi;
vi = vdev;
DPRINTF(("vtinput: device reset requested!\n"));
vi->ready = false;
virtio_reset_dev(&vi->base);
}
static void
virtio_input_neg_features(void *vdev, uint64_t negotiated_features)
{
struct virtio_input *vi = vdev;
vi->features = negotiated_features;
}
static void
virtio_input_set_status(void *vdev, uint64_t status)
{
struct virtio_input *vi = vdev;
if (status & VIRTIO_CONFIG_S_DRIVER_OK) {
if (!vi->ready)
vi->ready = true;
}
}
static int
virtio_input_cfgread(void *vdev, int offset, int size, uint32_t *retval)
{
struct virtio_input *vi = vdev;
struct virtio_input_config cfg;
bool rc;
rc = virtio_input_get_config(vi, vi->cfg.select,
vi->cfg.subsel, &cfg);
if (rc)
memcpy(retval, (uint8_t *)&cfg + offset, size);
else
memset(retval, 0, size);
return 0;
}
static int
virtio_input_cfgwrite(void *vdev, int offset, int size, uint32_t val)
{
struct virtio_input *vi = vdev;
if (offset == offsetof(struct virtio_input_config, select))
vi->cfg.select = (uint8_t)val;
else if (offset == offsetof(struct virtio_input_config, subsel))
vi->cfg.subsel = (uint8_t)val;
else
DPRINTF(("vtinput: write to readonly reg %d\n", offset));
return 0;
}
static bool
virtio_input_ignore_event(struct virtio_input_event *event)
{
if (!event)
return true;
/*
* EV_MSC is configured as INPUT_PASS_TO_ALL. In the use case of
* virtio-input, there is a loop as follows:
* - A mt frame with (EV_MSC,*,*) is passed to FE.
* - FE will call virtinput_status to pass (EV_MSC,*,*) back to BE.
* - BE writes this event to evdev. Because (EV_MSC,*,*)
* is configured as INPUT_PASS_TO_ALL, it will be written into
* the event buffer of evdev then be read out by BE without
* SYN followed.
* - Each mt frame will introduce one (EV_MSC,*,*).
* Later the frame becomes larger and larger...
*/
if (event->type == EV_MSC)
return true;
return false;
}
static void
virtio_input_notify_event_vq(void *vdev, struct virtio_vq_info *vq)
{
DPRINTF(("%s\n", __func__));
}
static void
virtio_input_notify_status_vq(void *vdev, struct virtio_vq_info *vq)
{
struct virtio_input *vi;
struct virtio_input_event event;
struct input_event host_event;
struct iovec iov;
int n, len;
uint16_t idx;
vi = vdev;
while (vq_has_descs(vq)) {
n = vq_getchain(vq, &idx, &iov, 1, NULL);
if (n < 0) {
WPRINTF(("virtio_input: invalid descriptors\n"));
return;
}
if (n == 0) {
WPRINTF(("virtio_input: get no available descriptors\n"));
return;
}
if (n != 1) {
WPRINTF(("virtio_input: get wrong number of available descriptors\n"));
vq_relchain(vq, idx, sizeof(event)); /* Release the chain */
return;
}
if (vi->fd > 0) {
memcpy(&event, iov.iov_base, sizeof(event));
if (!virtio_input_ignore_event(&event)) {
host_event.type = event.type;
host_event.code = event.code;
host_event.value = event.value;
if (gettimeofday(&host_event.time, NULL)) {
WPRINTF(("vtinput: gettimeofday failed\n"));
break;
}
len = write(vi->fd, &host_event, sizeof(host_event));
if (len == -1)
WPRINTF(("%s: write failed, len = %d, "
"errno = %d\n",
__func__, len, errno));
}
}
vq_relchain(vq, idx, sizeof(event)); /* Release the chain */
}
vq_endchains(vq, 1); /* Generate interrupt if appropriate. */
}
static void
virtio_input_send_event(struct virtio_input *vi,
struct virtio_input_event *event)
{
struct virtio_vq_info *vq;
struct iovec iov;
int n, i;
uint16_t idx;
if (!vi->ready)
return;
if (vi->event_qindex == vi->event_qsize) {
vi->event_qsize++;
vi->event_queue = realloc(vi->event_queue,
vi->event_qsize *
sizeof(struct virtio_input_event_elem));
if (!vi->event_queue) {
WPRINTF(("virtio_input: realloc memory for vi->event_queue failed!\n"));
return;
}
}
vi->event_queue[vi->event_qindex].event = *event;
vi->event_qindex++;
if (event->type != EV_SYN || event->code != SYN_REPORT)
return;
vq = &vi->queues[VIRTIO_INPUT_EVENT_QUEUE];
for (i = 0; i < vi->event_qindex; i++) {
if (!vq_has_descs(vq)) {
while (i-- > 0)
vq_retchain(vq);
WPRINTF(("%s: not enough avail descs, dropped:%d\n",
__func__, vi->event_qindex));
goto out;
}
n = vq_getchain(vq, &idx, &iov, 1, NULL);
if (n < 0) {
WPRINTF(("virtio-input: invalid descriptors\n"));
return;
}
if (n == 0) {
WPRINTF(("virtio-input: get no available desciptors\n"));
return;
}
if (n != 1) {
WPRINTF(("virtio_input: get wrong number of available descriptors\n"));
vq_relchain(vq, idx, sizeof(event)); /* Release the chain */
return;
}
vi->event_queue[i].iov = iov;
vi->event_queue[i].idx = idx;
}
for (i = 0; i < vi->event_qindex; i++) {
memcpy(vi->event_queue[i].iov.iov_base,
&vi->event_queue[i].event,
sizeof(struct virtio_input_event));
vq_relchain(vq, vi->event_queue[i].idx,
sizeof(struct virtio_input_event));
}
out:
vi->event_qindex = 0;
vq_endchains(vq, 1);
}
static void
virtio_input_read_event(int fd __attribute__((unused)),
enum ev_type t __attribute__((unused)),
void *arg)
{
struct virtio_input *vi = arg;
struct virtio_input_event event;
struct input_event host_event;
int len;
while (1) {
len = read(vi->fd, &host_event, sizeof(host_event));
if (len != sizeof(host_event)) {
if (len == -1 && errno != EAGAIN)
WPRINTF(("vtinput: host read failed! "
"len = %d, errno = %d\n",
len, errno));
break;
}
event.type = host_event.type;
event.code = host_event.code;
event.value = host_event.value;
virtio_input_send_event(vi, &event);
}
}
static int
virtio_input_get_bitmap(struct virtio_input *vi, unsigned int cmd, int count,
struct virtio_input_config *cfg)
{
int i, size = -1;
int rc;
if (count <= 0)
return -1;
if (!cfg)
return -1;
memset(cfg, 0, sizeof(*cfg));
rc = ioctl(vi->fd, cmd, cfg->u.bitmap);
if (rc < 0)
return -1;
count = count / 8;
for (i = count - 1; i >= 0; i--) {
if (cfg->u.bitmap[i]) {
size = i + 1;
break;
}
}
return size;
}
static bool
virtio_input_get_propbits(struct virtio_input *vi,
struct virtio_input_config *cfg)
{
unsigned int cmd;
int size;
if (!cfg)
return false;
cmd = EVIOCGPROP(INPUT_PROP_CNT / 8);
size = virtio_input_get_bitmap(vi, cmd, INPUT_PROP_CNT, cfg);
if (size > 0) {
cfg->select = VIRTIO_INPUT_CFG_PROP_BITS;
cfg->subsel = 0;
cfg->size = size;
return true;
}
return false;
}
static bool
virtio_input_get_evbits(struct virtio_input *vi, int type,
struct virtio_input_config *cfg)
{
unsigned int cmd;
int count, size;
if (!cfg)
return false;
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 false;
}
cmd = EVIOCGBIT(type, count / 8);
size = virtio_input_get_bitmap(vi, cmd, count, cfg);
if (size > 0) {
cfg->select = VIRTIO_INPUT_CFG_EV_BITS;
cfg->subsel = type;
cfg->size = size;
return true;
}
return false;
}
static bool
virtio_input_get_absinfo(struct virtio_input *vi, int axis,
struct virtio_input_config *cfg)
{
struct virtio_input_config ev_cfg;
struct input_absinfo abs;
bool has_ev_abs;
int rc;
if (!cfg)
return false;
has_ev_abs = virtio_input_get_evbits(vi, EV_ABS, &ev_cfg);
if (!has_ev_abs)
return false;
rc = ioctl(vi->fd, EVIOCGABS(axis), &abs);
if (rc < 0)
return false;
cfg->u.abs.min = abs.minimum;
cfg->u.abs.max = abs.maximum;
cfg->u.abs.fuzz = abs.fuzz;
cfg->u.abs.flat = abs.flat;
cfg->u.abs.res = abs.resolution;
cfg->select = VIRTIO_INPUT_CFG_ABS_INFO;
cfg->subsel = axis;
cfg->size = sizeof(struct virtio_input_absinfo);
return true;
}
static bool
virtio_input_get_config(struct virtio_input *vi, uint8_t select,
uint8_t subsel, struct virtio_input_config *cfg)
{
struct input_id dev_ids;
bool found = false;
int rc;
if (!cfg)
return false;
memset(cfg, 0, sizeof(*cfg));
switch (select) {
case VIRTIO_INPUT_CFG_ID_NAME:
rc = ioctl(vi->fd, EVIOCGNAME(sizeof(cfg->u.string) - 1),
cfg->u.string);
if (rc >= 0) {
cfg->select = VIRTIO_INPUT_CFG_ID_NAME;
cfg->size = strnlen(cfg->u.string,
sizeof(cfg->u.string));
found = true;
}
break;
case VIRTIO_INPUT_CFG_ID_SERIAL:
if (vi->serial) {
cfg->select = VIRTIO_INPUT_CFG_ID_SERIAL;
cfg->size = snprintf(cfg->u.string,
sizeof(cfg->u.string), "%s", vi->serial);
found = true;
}
break;
case VIRTIO_INPUT_CFG_ID_DEVIDS:
rc = ioctl(vi->fd, EVIOCGID, &dev_ids);
if (!rc) {
cfg->u.ids.bustype = dev_ids.bustype;
cfg->u.ids.vendor = dev_ids.vendor;
cfg->u.ids.product = dev_ids.product;
cfg->u.ids.version = dev_ids.version;
cfg->select = VIRTIO_INPUT_CFG_ID_DEVIDS;
cfg->size = sizeof(struct virtio_input_devids);
found = true;
}
break;
case VIRTIO_INPUT_CFG_PROP_BITS:
found = virtio_input_get_propbits(vi, cfg);
break;
case VIRTIO_INPUT_CFG_EV_BITS:
found = virtio_input_get_evbits(vi, subsel, cfg);
break;
case VIRTIO_INPUT_CFG_ABS_INFO:
found = virtio_input_get_absinfo(vi, subsel, cfg);
break;
default:
break;
}
return found;
}
static void
virtio_input_teardown(void *param)
{
struct virtio_input *vi;
vi = (struct virtio_input *)param;
if (vi) {
pthread_mutex_destroy(&vi->mtx);
if (vi->event_queue)
free(vi->event_queue);
if (vi->fd > 0)
close(vi->fd);
if (vi->evdev)
free(vi->evdev);
if (vi->serial)
free(vi->serial);
virtio_input_reset(vi);
free(vi);
vi = NULL;
}
}
static int
virtio_input_init(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
{
struct virtio_input *vi;
pthread_mutexattr_t attr;
char *opt;
int flags, ver;
int rc;
/* get evdev path from opts
* -s n,virtio-input,/dev/input/eventX[,serial]
*/
if (!opts) {
WPRINTF(("%s: evdev path is NULL\n", __func__));
return -1;
}
vi = calloc(1, sizeof(struct virtio_input));
if (!vi) {
WPRINTF(("%s: out of memory\n", __func__));
return -1;
}
opt = strsep(&opts, ",");
if (!opt) {
WPRINTF(("%s: evdev path is NULL\n", __func__));
goto opt_fail;
}
vi->evdev = strdup(opt);
if (!vi->evdev) {
WPRINTF(("%s: strdup failed\n", __func__));
goto opt_fail;
}
if (opts) {
vi->serial = strdup(opts);
if (!vi->serial) {
WPRINTF(("%s: strdup serial failed\n", __func__));
goto serial_fail;
}
}
vi->fd = open(vi->evdev, O_RDWR);
if (vi->fd < 0) {
WPRINTF(("open %s failed %d\n", vi->evdev, errno));
goto open_fail;
}
flags = fcntl(vi->fd, F_GETFL);
fcntl(vi->fd, F_SETFL, flags | O_NONBLOCK);
rc = ioctl(vi->fd, EVIOCGVERSION, &ver); /* is it a evdev device? */
if (rc < 0) {
WPRINTF(("%s: get version failed\n", vi->evdev));
goto evdev_fail;
}
rc = ioctl(vi->fd, EVIOCGRAB, 1); /* exclusive access */
if (rc < 0) {
WPRINTF(("%s: grab device failed %d\n", vi->evdev, errno));
goto evdev_fail;
}
/* init mutex attribute properly to avoid deadlock */
rc = pthread_mutexattr_init(&attr);
if (rc)
DPRINTF(("mutexattr init failed with erro %d!\n", rc));
rc = pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
if (rc)
DPRINTF(("vtinput: mutexattr_settype failed with "
"error %d!\n", rc));
rc = pthread_mutex_init(&vi->mtx, &attr);
if (rc)
DPRINTF(("vtinput: pthread_mutex_init failed with "
"error %d!\n", rc));
vi->event_qsize = VIRTIO_INPUT_PACKET_SIZE;
vi->event_qindex = 0;
vi->event_queue = calloc(vi->event_qsize,
sizeof(struct virtio_input_event_elem));
if (!vi->event_queue) {
WPRINTF(("vtinput: could not alloc event queue buf\n"));
goto evqueue_fail;
}
vi->mevp = mevent_add(vi->fd, EVF_READ, virtio_input_read_event, vi,
virtio_input_teardown, vi);
if (vi->mevp == NULL) {
WPRINTF(("vtinput: could not register event\n"));
goto mevent_fail;
}
virtio_linkup(&vi->base, &virtio_input_ops, vi, dev, vi->queues, BACKEND_VBSU);
vi->base.mtx = &vi->mtx;
vi->base.device_caps = VIRTIO_INPUT_S_HOSTCAPS;
vi->queues[VIRTIO_INPUT_EVENT_QUEUE].qsize = VIRTIO_INPUT_RINGSZ;
vi->queues[VIRTIO_INPUT_EVENT_QUEUE].notify =
virtio_input_notify_event_vq;
vi->queues[VIRTIO_INPUT_STATUS_QUEUE].qsize = VIRTIO_INPUT_RINGSZ;
vi->queues[VIRTIO_INPUT_STATUS_QUEUE].notify =
virtio_input_notify_status_vq;
/* initialize config space */
pci_set_cfgdata16(dev, PCIR_DEVICE, 0x1040 + VIRTIO_TYPE_INPUT);
pci_set_cfgdata16(dev, PCIR_VENDOR, VIRTIO_VENDOR);
pci_set_cfgdata8(dev, PCIR_CLASS, PCIC_INPUTDEV);
pci_set_cfgdata8(dev, PCIR_SUBCLASS, PCIS_INPUTDEV_OTHER);
pci_set_cfgdata16(dev, PCIR_SUBDEV_0, 0x1100);
if (is_winvm == true)
pci_set_cfgdata16(dev, PCIR_SUBVEND_0, ORACLE_VENDOR_ID);
else
pci_set_cfgdata16(dev, PCIR_SUBVEND_0, VIRTIO_VENDOR);
pci_set_cfgdata16(dev, PCIR_REVID, 1);
if (virtio_interrupt_init(&vi->base, virtio_uses_msix())) {
DPRINTF(("%s, interrupt_init failed!\n", __func__));
goto fail;
}
rc = virtio_set_modern_bar(&vi->base, true);
return rc;
fail:
/* all resources will be freed in the teardown callback */
mevent_delete(vi->mevp);
return -1;
mevent_fail:
free(vi->event_queue);
vi->event_queue = NULL;
evqueue_fail:
pthread_mutex_destroy(&vi->mtx);
evdev_fail:
close(vi->fd);
vi->fd = -1;
open_fail:
if (vi->serial) {
free(vi->serial);
vi->serial = NULL;
}
serial_fail:
free(vi->evdev);
vi->evdev = NULL;
opt_fail:
free(vi);
vi = NULL;
return -1;
}
static void
virtio_input_deinit(struct vmctx *ctx, struct pci_vdev *dev, char *opts)
{
struct virtio_input *vi;
vi = (struct virtio_input *)dev->arg;
if (vi && vi->mevp)
mevent_delete(vi->mevp);
}
struct pci_vdev_ops pci_ops_virtio_input = {
.class_name = "virtio-input",
.vdev_init = virtio_input_init,
.vdev_deinit = virtio_input_deinit,
.vdev_barwrite = virtio_pci_write,
.vdev_barread = virtio_pci_read
};
DEFINE_PCI_DEVTYPE(pci_ops_virtio_input);