/* * This work is licensed under the terms of the GNU GPL, version 2 or * (at your option) any later version. See the COPYING file in the * top-level directory. */ #include "qemu/osdep.h" #include #include "qemu/iov.h" #include "qemu/bswap.h" #include "qemu/sockets.h" #include "libvhost-user-glib.h" #include "standard-headers/linux/input.h" #include "standard-headers/linux/virtio_input.h" #include "qapi/error.h" enum { VHOST_USER_INPUT_MAX_QUEUES = 2, }; typedef struct virtio_input_event virtio_input_event; typedef struct virtio_input_config virtio_input_config; typedef struct VuInput { VugDev dev; GSource *evsrc; int evdevfd; GArray *config; virtio_input_config *sel_config; struct { virtio_input_event event; VuVirtqElement *elem; } *queue; uint32_t qindex, qsize; } VuInput; static void vi_input_send(VuInput *vi, struct virtio_input_event *event) { VuDev *dev = &vi->dev.parent; VuVirtq *vq = vu_get_queue(dev, 0); VuVirtqElement *elem; int i, len; /* queue up events ... */ if (vi->qindex == vi->qsize) { vi->qsize++; vi->queue = g_realloc_n(vi->queue, vi->qsize, sizeof(vi->queue[0])); } vi->queue[vi->qindex++].event = *event; /* ... until we see a report sync ... */ if (event->type != htole16(EV_SYN) || event->code != htole16(SYN_REPORT)) { return; } /* ... then check available space ... */ for (i = 0; i < vi->qindex; i++) { elem = vu_queue_pop(dev, vq, sizeof(VuVirtqElement)); if (!elem) { while (--i >= 0) { vu_queue_unpop(dev, vq, vi->queue[i].elem, 0); } vi->qindex = 0; g_warning("virtio-input queue full"); return; } vi->queue[i].elem = elem; } /* ... and finally pass them to the guest */ for (i = 0; i < vi->qindex; i++) { elem = vi->queue[i].elem; len = iov_from_buf(elem->in_sg, elem->in_num, 0, &vi->queue[i].event, sizeof(virtio_input_event)); vu_queue_push(dev, vq, elem, len); free(elem); } vu_queue_notify(&vi->dev.parent, vq); vi->qindex = 0; } static void vi_evdev_watch(VuDev *dev, int condition, void *data) { VuInput *vi = data; int fd = vi->evdevfd; g_debug("Got evdev condition %x", condition); struct virtio_input_event virtio; struct input_event evdev; int rc; for (;;) { rc = read(fd, &evdev, sizeof(evdev)); if (rc != sizeof(evdev)) { break; } g_debug("input %d %d %d", evdev.type, evdev.code, evdev.value); virtio.type = htole16(evdev.type); virtio.code = htole16(evdev.code); virtio.value = htole32(evdev.value); vi_input_send(vi, &virtio); } } static void vi_handle_status(VuInput *vi, virtio_input_event *event) { struct input_event evdev; struct timeval tval; int rc; if (gettimeofday(&tval, NULL)) { perror("vi_handle_status: gettimeofday"); return; } evdev.input_event_sec = tval.tv_sec; evdev.input_event_usec = tval.tv_usec; evdev.type = le16toh(event->type); evdev.code = le16toh(event->code); evdev.value = le32toh(event->value); rc = write(vi->evdevfd, &evdev, sizeof(evdev)); if (rc == -1) { perror("vi_host_handle_status: write"); } } static void vi_handle_sts(VuDev *dev, int qidx) { VuInput *vi = container_of(dev, VuInput, dev.parent); VuVirtq *vq = vu_get_queue(dev, qidx); virtio_input_event event; VuVirtqElement *elem; int len; g_debug("%s", G_STRFUNC); for (;;) { elem = vu_queue_pop(dev, vq, sizeof(VuVirtqElement)); if (!elem) { break; } memset(&event, 0, sizeof(event)); len = iov_to_buf(elem->out_sg, elem->out_num, 0, &event, sizeof(event)); vi_handle_status(vi, &event); vu_queue_push(dev, vq, elem, len); free(elem); } vu_queue_notify(&vi->dev.parent, vq); } static void vi_panic(VuDev *dev, const char *msg) { g_critical("%s\n", msg); exit(EXIT_FAILURE); } static void vi_queue_set_started(VuDev *dev, int qidx, bool started) { VuInput *vi = container_of(dev, VuInput, dev.parent); VuVirtq *vq = vu_get_queue(dev, qidx); g_debug("queue started %d:%d", qidx, started); if (qidx == 1) { vu_set_queue_handler(dev, vq, started ? vi_handle_sts : NULL); } started = vu_queue_started(dev, vu_get_queue(dev, 0)) && vu_queue_started(dev, vu_get_queue(dev, 1)); if (started && !vi->evsrc) { vi->evsrc = vug_source_new(&vi->dev, vi->evdevfd, G_IO_IN, vi_evdev_watch, vi); } if (!started && vi->evsrc) { vug_source_destroy(vi->evsrc); vi->evsrc = NULL; } } static virtio_input_config * vi_find_config(VuInput *vi, uint8_t select, uint8_t subsel) { virtio_input_config *cfg; int i; for (i = 0; i < vi->config->len; i++) { cfg = &g_array_index(vi->config, virtio_input_config, i); if (select == cfg->select && subsel == cfg->subsel) { return cfg; } } return NULL; } static int vi_get_config(VuDev *dev, uint8_t *config, uint32_t len) { VuInput *vi = container_of(dev, VuInput, dev.parent); if (len > sizeof(*vi->sel_config)) { return -1; } if (vi->sel_config) { memcpy(config, vi->sel_config, len); } else { memset(config, 0, len); } return 0; } static int vi_set_config(VuDev *dev, const uint8_t *data, uint32_t offset, uint32_t size, uint32_t flags) { VuInput *vi = container_of(dev, VuInput, dev.parent); virtio_input_config *config = (virtio_input_config *)data; vi->sel_config = vi_find_config(vi, config->select, config->subsel); return 0; } static const VuDevIface vuiface = { .queue_set_started = vi_queue_set_started, .get_config = vi_get_config, .set_config = vi_set_config, }; static void vi_bits_config(VuInput *vi, int type, int count) { virtio_input_config bits; int rc, i, size = 0; memset(&bits, 0, sizeof(bits)); rc = ioctl(vi->evdevfd, EVIOCGBIT(type, count / 8), bits.u.bitmap); if (rc < 0) { return; } for (i = 0; i < count / 8; i++) { if (bits.u.bitmap[i]) { size = i + 1; } } if (size == 0) { return; } bits.select = VIRTIO_INPUT_CFG_EV_BITS; bits.subsel = type; bits.size = size; g_array_append_val(vi->config, bits); } static char *opt_evdev; static int opt_fdnum = -1; static char *opt_socket_path; static gboolean opt_nograb; static gboolean opt_print_caps; static GOptionEntry entries[] = { { "print-capabilities", 'c', 0, G_OPTION_ARG_NONE, &opt_print_caps, "Print capabilities", NULL }, { "no-grab", 'n', 0, G_OPTION_ARG_NONE, &opt_nograb, "Don't grab device", NULL }, { "fd", 'f', 0, G_OPTION_ARG_INT, &opt_fdnum, "Use inherited fd socket", "FDNUM" }, { "socket-path", 's', 0, G_OPTION_ARG_FILENAME, &opt_socket_path, "Use UNIX socket path", "PATH" }, { "evdev-path", 'p', 0, G_OPTION_ARG_FILENAME, &opt_evdev, "evdev input device path", "PATH" }, { NULL, } }; int main(int argc, char *argv[]) { GMainLoop *loop = NULL; VuInput vi = { 0, }; int rc, ver, fd; virtio_input_config id; struct input_id ids; GError *error = NULL; GOptionContext *context; context = g_option_context_new(NULL); g_option_context_add_main_entries(context, entries, NULL); if (!g_option_context_parse(context, &argc, &argv, &error)) { g_printerr("Option parsing failed: %s\n", error->message); exit(EXIT_FAILURE); } if (opt_print_caps) { g_print("{\n"); g_print(" \"type\": \"input\",\n"); g_print(" \"features\": [\n"); g_print(" \"evdev-path\",\n"); g_print(" \"no-grab\"\n"); g_print(" ]\n"); g_print("}\n"); exit(EXIT_SUCCESS); } if (!opt_evdev) { g_printerr("Please specify an evdev path\n"); exit(EXIT_FAILURE); } if ((!!opt_socket_path + (opt_fdnum != -1)) != 1) { g_printerr("Please specify either --fd or --socket-path\n"); exit(EXIT_FAILURE); } vi.evdevfd = open(opt_evdev, O_RDWR); if (vi.evdevfd < 0) { g_printerr("Failed to open evdev: %s\n", g_strerror(errno)); exit(EXIT_FAILURE); } rc = ioctl(vi.evdevfd, EVIOCGVERSION, &ver); if (rc < 0) { g_printerr("%s: is not an evdev device\n", argv[1]); exit(EXIT_FAILURE); } if (!opt_nograb) { rc = ioctl(vi.evdevfd, EVIOCGRAB, 1); if (rc < 0) { g_printerr("Failed to grab device\n"); exit(EXIT_FAILURE); } } vi.config = g_array_new(false, false, sizeof(virtio_input_config)); memset(&id, 0, sizeof(id)); if (ioctl(vi.evdevfd, EVIOCGNAME(sizeof(id.u.string) - 1), id.u.string) < 0) { g_printerr("Failed to get evdev name: %s\n", g_strerror(errno)); exit(EXIT_FAILURE); } id.select = VIRTIO_INPUT_CFG_ID_NAME; id.size = strlen(id.u.string); g_array_append_val(vi.config, id); if (ioctl(vi.evdevfd, EVIOCGID, &ids) == 0) { memset(&id, 0, sizeof(id)); id.select = VIRTIO_INPUT_CFG_ID_DEVIDS; id.size = sizeof(struct virtio_input_devids); id.u.ids.bustype = cpu_to_le16(ids.bustype); id.u.ids.vendor = cpu_to_le16(ids.vendor); id.u.ids.product = cpu_to_le16(ids.product); id.u.ids.version = cpu_to_le16(ids.version); g_array_append_val(vi.config, id); } vi_bits_config(&vi, EV_KEY, KEY_CNT); vi_bits_config(&vi, EV_REL, REL_CNT); vi_bits_config(&vi, EV_ABS, ABS_CNT); vi_bits_config(&vi, EV_MSC, MSC_CNT); vi_bits_config(&vi, EV_SW, SW_CNT); g_debug("config length: %u", vi.config->len); if (opt_socket_path) { int lsock = unix_listen(opt_socket_path, &error_fatal); if (lsock < 0) { g_printerr("Failed to listen on %s.\n", opt_socket_path); exit(EXIT_FAILURE); } fd = accept(lsock, NULL, NULL); close(lsock); } else { fd = opt_fdnum; } if (fd == -1) { g_printerr("Invalid vhost-user socket.\n"); exit(EXIT_FAILURE); } if (!vug_init(&vi.dev, VHOST_USER_INPUT_MAX_QUEUES, fd, vi_panic, &vuiface)) { g_printerr("Failed to initialize libvhost-user-glib.\n"); exit(EXIT_FAILURE); } loop = g_main_loop_new(NULL, FALSE); g_main_loop_run(loop); g_main_loop_unref(loop); vug_deinit(&vi.dev); vug_source_destroy(vi.evsrc); g_array_free(vi.config, TRUE); g_free(vi.queue); return 0; }