Message ID | 1397120874-17166-6-git-send-email-kraxel@redhat.com |
---|---|
State | New |
Headers | show |
On Thu, Apr 10, 2014 at 11:07:51AM +0200, Gerd Hoffmann wrote: > This patch adds virtio-input support to qemu. It brings a abstract > base class providing core support, other classes can build on it to > actually implement input devices. > > virtio-input basically sends linux input layer events (evdev) over > virtio. > > Signed-off-by: Gerd Hoffmann <kraxel@redhat.com> > --- > hw/input/Makefile.objs | 4 + > hw/input/virtio-input.c | 253 +++++++++++++++++++++++++++++++++++++++ > hw/virtio/virtio-pci.c | 36 ++++++ > hw/virtio/virtio-pci.h | 14 +++ > include/hw/virtio/virtio-input.h | 105 ++++++++++++++++ > include/hw/virtio/virtio.h | 1 + > 6 files changed, 413 insertions(+) > create mode 100644 hw/input/virtio-input.c > create mode 100644 include/hw/virtio/virtio-input.h > > diff --git a/hw/input/Makefile.objs b/hw/input/Makefile.objs > index e8c80b9..ee8bba9 100644 > --- a/hw/input/Makefile.objs > +++ b/hw/input/Makefile.objs > @@ -8,6 +8,10 @@ common-obj-$(CONFIG_STELLARIS_INPUT) += stellaris_input.o > common-obj-$(CONFIG_TSC2005) += tsc2005.o > common-obj-$(CONFIG_VMMOUSE) += vmmouse.o > > +ifeq ($(CONFIG_LINUX),y) > +common-obj-$(CONFIG_VIRTIO) += virtio-input.o > +endif > + > obj-$(CONFIG_MILKYMIST) += milkymist-softusb.o > obj-$(CONFIG_PXA2XX) += pxa2xx_keypad.o > obj-$(CONFIG_TSC210X) += tsc210x.o > diff --git a/hw/input/virtio-input.c b/hw/input/virtio-input.c > new file mode 100644 > index 0000000..35f0cfc > --- /dev/null > +++ b/hw/input/virtio-input.c > @@ -0,0 +1,253 @@ > +/* > + * 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/iov.h" > + > +#include "hw/qdev.h" > +#include "hw/virtio/virtio.h" > +#include "hw/virtio/virtio-input.h" > + > +#include "ui/console.h" > + > +#include <linux/input.h> > + > +/* ----------------------------------------------------------------- */ > + > +void virtio_input_send(VirtIOInput *vinput, virtio_input_event *event) > +{ > + VirtQueueElement elem; > + int len; > + > + if (!virtqueue_pop(vinput->evt, &elem)) { > + fprintf(stderr, "%s: virtqueue empty, dropping event\n", __func__); > + return; Looks scary. > + } > + len = iov_from_buf(elem.in_sg, elem.in_num, > + 0, event, sizeof(*event)); > + virtqueue_push(vinput->evt, &elem, len); > +} > + > +static void virtio_input_handle_evt(VirtIODevice *vdev, VirtQueue *vq) > +{ > + /* nothing */ > +} > + > +static void virtio_input_handle_sts(VirtIODevice *vdev, VirtQueue *vq) > +{ > + VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(vdev); > + VirtIOInput *vinput = VIRTIO_INPUT(vdev); > + virtio_input_event event; > + VirtQueueElement elem; > + int len; > + > + while (virtqueue_pop(vinput->sts, &elem)) { > + memset(&event, 0, sizeof(event)); > + len = iov_to_buf(elem.out_sg, elem.out_num, > + 0, &event, sizeof(event)); > + if (vic->handle_status) { > + vic->handle_status(vinput, &event); > + } > + virtqueue_push(vinput->sts, &elem, len); > + } > + virtio_notify(vdev, vinput->sts); > +} > + > +static virtio_input_config *virtio_input_find_config(VirtIOInput *vinput, > + uint8_t select, > + uint8_t subsel) > +{ > + VirtIOInputConfig *cfg; > + > + QTAILQ_FOREACH(cfg, &vinput->cfg_list, node) { > + if (select == cfg->config.select && > + subsel == cfg->config.subsel) { > + return &cfg->config; > + } > + } > + return NULL; > +} > + > +void virtio_input_add_config(VirtIOInput *vinput, > + virtio_input_config *config) > +{ > + VirtIOInputConfig *cfg; > + > + if (virtio_input_find_config(vinput, config->select, config->subsel)) { > + /* should not happen */ > + fprintf(stderr, "%s: duplicate config: %d/%d\n", > + __func__, config->select, config->subsel); > + abort(); > + } > + > + cfg = g_new0(VirtIOInputConfig, 1); > + cfg->config = *config; > + QTAILQ_INSERT_TAIL(&vinput->cfg_list, cfg, node); > +} > + > +void virtio_input_init_config(VirtIOInput *vinput, > + virtio_input_config *config) > +{ > + int i = 0; > + > + QTAILQ_INIT(&vinput->cfg_list); > + while (config[i].select) { > + virtio_input_add_config(vinput, config + i); > + i++; > + } > +} > + > +void virtio_input_idstr_config(VirtIOInput *vinput, > + uint8_t select, const char *string) > +{ > + virtio_input_config id; > + > + if (!string) { > + return; > + } > + memset(&id, 0, sizeof(id)); > + id.select = select; > + id.size = snprintf(id.u.string, sizeof(id.u.string), "%s", string); > + virtio_input_add_config(vinput, &id); > +} > + > +static void virtio_input_get_config(VirtIODevice *vdev, uint8_t *config_data) > +{ > + VirtIOInput *vinput = VIRTIO_INPUT(vdev); > + virtio_input_config *config; > + > + config = virtio_input_find_config(vinput, vinput->cfg_select, > + vinput->cfg_subsel); > + if (config) { > + memcpy(config_data, config, vinput->cfg_size); > + } else { > + memset(config_data, 0, vinput->cfg_size); > + } > +} > + > +static void virtio_input_set_config(VirtIODevice *vdev, > + const uint8_t *config_data) > +{ > + VirtIOInput *vinput = VIRTIO_INPUT(vdev); > + virtio_input_config *config = (virtio_input_config *)config_data; > + > + vinput->cfg_select = config->select; > + vinput->cfg_subsel = config->subsel; > + virtio_notify_config(vdev); > +} > + > +static uint32_t virtio_input_get_features(VirtIODevice *vdev, uint32_t f) > +{ > + return f; > +} > + > +static void virtio_input_set_status(VirtIODevice *vdev, uint8_t val) > +{ > + VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(vdev); > + VirtIOInput *vinput = VIRTIO_INPUT(vdev); > + > + if (val & VIRTIO_CONFIG_S_DRIVER_OK) { > + if (!vinput->active) { > + vinput->active = true; > + if (vic->change_active) { > + vic->change_active(vinput); > + } > + } > + } > +} > + > +static void virtio_input_reset(VirtIODevice *vdev) > +{ > + VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(vdev); > + VirtIOInput *vinput = VIRTIO_INPUT(vdev); > + > + if (vinput->active) { > + vinput->active = false; > + if (vic->change_active) { > + vic->change_active(vinput); > + } > + } > +} > + > +static void virtio_input_device_realize(DeviceState *dev, Error **errp) > +{ > + VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(dev); > + VirtIODevice *vdev = VIRTIO_DEVICE(dev); > + VirtIOInput *vinput = VIRTIO_INPUT(dev); > + VirtIOInputConfig *cfg; > + > + > + if (vic->unrealize) { > + vic->realize(dev, errp); > + if (error_is_set(errp)) { > + return; > + } > + } > + > + virtio_input_idstr_config(vinput, VIRTIO_INPUT_CFG_ID_SERIAL, > + vinput->input.serial); > + virtio_input_idstr_config(vinput, VIRTIO_INPUT_CFG_ID_SEAT, > + vinput->input.seat); > + > + QTAILQ_FOREACH(cfg, &vinput->cfg_list, node) { > + if (vinput->cfg_size < cfg->config.size) { > + vinput->cfg_size = cfg->config.size; > + } > + } > + vinput->cfg_size += 4; > + assert(vinput->cfg_size <= sizeof(virtio_input_config)); > + > + virtio_init(vdev, "virtio-input", VIRTIO_ID_INPUT, > + vinput->cfg_size); > + vinput->evt = virtio_add_queue(vdev, 64, virtio_input_handle_evt); > + vinput->sts = virtio_add_queue(vdev, 64, virtio_input_handle_sts); > +} > + > +static void virtio_input_device_unrealize(DeviceState *dev, Error **errp) > +{ > + VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(dev); > + VirtIODevice *vdev = VIRTIO_DEVICE(dev); > + > + if (vic->unrealize) { > + vic->unrealize(dev, errp); > + if (error_is_set(errp)) { > + return; > + } > + } > + virtio_cleanup(vdev); > +} > + > +static void virtio_input_class_init(ObjectClass *klass, void *data) > +{ > + DeviceClass *dc = DEVICE_CLASS(klass); > + VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); > + > + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); > + vdc->realize = virtio_input_device_realize; > + vdc->unrealize = virtio_input_device_unrealize; > + vdc->get_config = virtio_input_get_config; > + vdc->set_config = virtio_input_set_config; > + vdc->get_features = virtio_input_get_features; > + vdc->set_status = virtio_input_set_status; > + vdc->reset = virtio_input_reset; > +} > + > +static const TypeInfo virtio_input_info = { > + .name = TYPE_VIRTIO_INPUT, > + .parent = TYPE_VIRTIO_DEVICE, > + .instance_size = sizeof(VirtIOInput), > + .class_size = sizeof(VirtIOInputClass), > + .class_init = virtio_input_class_init, > + .abstract = true, > +}; > + > +/* ----------------------------------------------------------------- */ > + > +static void virtio_register_types(void) > +{ > + type_register_static(&virtio_input_info); > +} > + > +type_init(virtio_register_types) > diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c > index ce97514..5518192 100644 > --- a/hw/virtio/virtio-pci.c > +++ b/hw/virtio/virtio-pci.c > @@ -23,6 +23,7 @@ > #include "hw/virtio/virtio-serial.h" > #include "hw/virtio/virtio-scsi.h" > #include "hw/virtio/virtio-balloon.h" > +#include "hw/virtio/virtio-input.h" > #include "hw/pci/pci.h" > #include "qemu/error-report.h" > #include "hw/pci/msi.h" > @@ -1531,6 +1532,40 @@ static const TypeInfo virtio_rng_pci_info = { > .class_init = virtio_rng_pci_class_init, > }; > > +/* virtio-input-pci */ > + > +static int virtio_input_pci_init(VirtIOPCIProxy *vpci_dev) > +{ > + VirtIOInputPCI *vinput = VIRTIO_INPUT_PCI(vpci_dev); > + DeviceState *vdev = DEVICE(&vinput->vdev); > + > + qdev_set_parent_bus(vdev, BUS(&vpci_dev->bus)); > + return qdev_init(vdev); > +} > + > +static void virtio_input_pci_class_init(ObjectClass *klass, void *data) > +{ > + DeviceClass *dc = DEVICE_CLASS(klass); > + VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass); > + PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass); > + > + k->init = virtio_input_pci_init; > + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); > + > + pcidev_k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET; > + pcidev_k->device_id = PCI_DEVICE_ID_VIRTIO_INPUT; > + pcidev_k->revision = VIRTIO_PCI_ABI_VERSION; > + pcidev_k->class_id = PCI_CLASS_OTHERS; > +} > + > +static const TypeInfo virtio_input_pci_info = { > + .name = TYPE_VIRTIO_INPUT_PCI, > + .parent = TYPE_VIRTIO_PCI, > + .instance_size = sizeof(VirtIOInputPCI), > + .class_init = virtio_input_pci_class_init, > + .abstract = true, > +}; > + > /* virtio-pci-bus */ > > static void virtio_pci_bus_new(VirtioBusState *bus, size_t bus_size, > @@ -1575,6 +1610,7 @@ static const TypeInfo virtio_pci_bus_info = { > static void virtio_pci_register_types(void) > { > type_register_static(&virtio_rng_pci_info); > + type_register_static(&virtio_input_pci_info); > type_register_static(&virtio_pci_bus_info); > type_register_static(&virtio_pci_info); > #ifdef CONFIG_VIRTFS > diff --git a/hw/virtio/virtio-pci.h b/hw/virtio/virtio-pci.h > index dc332ae..f1e75ad 100644 > --- a/hw/virtio/virtio-pci.h > +++ b/hw/virtio/virtio-pci.h > @@ -24,6 +24,7 @@ > #include "hw/virtio/virtio-balloon.h" > #include "hw/virtio/virtio-bus.h" > #include "hw/virtio/virtio-9p.h" > +#include "hw/virtio/virtio-input.h" > #ifdef CONFIG_VIRTFS > #include "hw/9pfs/virtio-9p.h" > #endif > @@ -39,6 +40,7 @@ typedef struct VirtIOSerialPCI VirtIOSerialPCI; > typedef struct VirtIONetPCI VirtIONetPCI; > typedef struct VHostSCSIPCI VHostSCSIPCI; > typedef struct VirtIORngPCI VirtIORngPCI; > +typedef struct VirtIOInputPCI VirtIOInputPCI; > > /* virtio-pci-bus */ > > @@ -199,6 +201,18 @@ struct VirtIORngPCI { > VirtIORNG vdev; > }; > > +/* > + * virtio-input-pci: This extends VirtioPCIProxy. > + */ > +#define TYPE_VIRTIO_INPUT_PCI "virtio-input-pci" > +#define VIRTIO_INPUT_PCI(obj) \ > + OBJECT_CHECK(VirtIOInputPCI, (obj), TYPE_VIRTIO_INPUT_PCI) > + > +struct VirtIOInputPCI { > + VirtIOPCIProxy parent_obj; > + VirtIOInput vdev; > +}; > + > /* Virtio ABI version, if we increment this, we break the guest driver. */ > #define VIRTIO_PCI_ABI_VERSION 0 > > diff --git a/include/hw/virtio/virtio-input.h b/include/hw/virtio/virtio-input.h > new file mode 100644 > index 0000000..5d37a70 > --- /dev/null > +++ b/include/hw/virtio/virtio-input.h > @@ -0,0 +1,105 @@ > +#ifndef _QEMU_VIRTIO_INPUT_H > +#define _QEMU_VIRTIO_INPUT_H > + > +#include "ui/input.h" > + > +/* ----------------------------------------------------------------- */ > +/* virtio input protocol */ > + > +/* The Virtio ID for the virtio input device */ > +#define VIRTIO_ID_INPUT 18 > + > +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_SEAT = 0x03, > + VIRTIO_INPUT_CFG_PROP_BITS = 0x10, > + VIRTIO_INPUT_CFG_EV_BITS = 0x11, > + VIRTIO_INPUT_CFG_ABS_INFO = 0x12, > +}; > + > +typedef struct virtio_input_absinfo { > + uint32_t min; > + uint32_t max; > + uint32_t fuzz; > + uint32_t flat; > +} virtio_input_absinfo; > + > +typedef struct virtio_input_config { > + uint8_t select; > + uint8_t subsel; > + uint8_t size; > + uint8_t reserved; > + union { > + char string[128]; > + uint8_t bitmap[128]; > + virtio_input_absinfo abs; > + } u; > +} virtio_input_config; > + > +typedef struct virtio_input_event { > + uint16_t type; > + uint16_t code; > + int32_t value; > +} virtio_input_event; > + > +/* ----------------------------------------------------------------- */ > +/* qemu internals */ > + > +#define TYPE_VIRTIO_INPUT "virtio-input-device" > +#define VIRTIO_INPUT(obj) \ > + OBJECT_CHECK(VirtIOInput, (obj), TYPE_VIRTIO_INPUT) > +#define VIRTIO_INPUT_GET_PARENT_CLASS(obj) \ > + OBJECT_GET_PARENT_CLASS(obj, TYPE_VIRTIO_INPUT) > +#define VIRTIO_INPUT_GET_CLASS(obj) \ > + OBJECT_GET_CLASS(VirtIOInputClass, obj, TYPE_VIRTIO_INPUT) > +#define VIRTIO_INPUT_CLASS(klass) \ > + OBJECT_CLASS_CHECK(VirtIOInputClass, klass, TYPE_VIRTIO_INPUT) > + > +typedef struct VirtIOInput VirtIOInput; > +typedef struct VirtIOInputClass VirtIOInputClass; > +typedef struct VirtIOInputConfig VirtIOInputConfig; > + > +struct virtio_input_conf { > + char *serial; > + char *seat; > +}; > + > +struct VirtIOInputConfig { > + virtio_input_config config; > + QTAILQ_ENTRY(VirtIOInputConfig) node; > +}; > + > +struct VirtIOInput { > + VirtIODevice parent_obj; > + uint8_t cfg_select; > + uint8_t cfg_subsel; > + uint32_t cfg_size; > + QTAILQ_HEAD(, VirtIOInputConfig) cfg_list; > + VirtQueue *evt, *sts; > + virtio_input_conf input; > + > + bool active; > +}; > + > +struct VirtIOInputClass { > + /*< private >*/ > + VirtioDeviceClass parent; > + /*< public >*/ > + > + DeviceRealize realize; > + DeviceUnrealize unrealize; > + void (*change_active)(VirtIOInput *vinput); > + void (*handle_status)(VirtIOInput *vinput, virtio_input_event *event); > +}; > + > +void virtio_input_send(VirtIOInput *vinput, virtio_input_event *event); > +void virtio_input_init_config(VirtIOInput *vinput, > + virtio_input_config *config); > +void virtio_input_add_config(VirtIOInput *vinput, > + virtio_input_config *config); > +void virtio_input_idstr_config(VirtIOInput *vinput, > + uint8_t select, const char *string); > + > +#endif /* _QEMU_VIRTIO_INPUT_H */ > diff --git a/include/hw/virtio/virtio.h b/include/hw/virtio/virtio.h > index 3e54e90..93d1607 100644 > --- a/include/hw/virtio/virtio.h > +++ b/include/hw/virtio/virtio.h > @@ -222,6 +222,7 @@ VirtIODevice *virtio_net_init(DeviceState *dev, NICConf *conf, > struct virtio_net_conf *net, > uint32_t host_features); > typedef struct virtio_serial_conf virtio_serial_conf; > +typedef struct virtio_input_conf virtio_input_conf; > typedef struct VirtIOSCSIConf VirtIOSCSIConf; > typedef struct VirtIORNGConf VirtIORNGConf; > > -- > 1.8.3.1
On Do, 2014-04-10 at 14:06 +0300, Michael S. Tsirkin wrote: > > +void virtio_input_send(VirtIOInput *vinput, virtio_input_event > *event) > > +{ > > + VirtQueueElement elem; > > + int len; > > + > > + if (!virtqueue_pop(vinput->evt, &elem)) { > > + fprintf(stderr, "%s: virtqueue empty, dropping event\n", > __func__); > > + return; > > Looks scary. > It's not different from other input devices. No buffer space -> drop event. What else do you think should happen? We could signal "you lost events" to the guest, but I suspect that buys us nothing. Other input devices don't have that capability, so guests are likely not prepared to handle the situation. Also, there isn't much they can actually do about it. cheers, Gerd
On Thu, Apr 10, 2014 at 02:22:17PM +0200, Gerd Hoffmann wrote: > On Do, 2014-04-10 at 14:06 +0300, Michael S. Tsirkin wrote: > > > +void virtio_input_send(VirtIOInput *vinput, virtio_input_event > > *event) > > > +{ > > > + VirtQueueElement elem; > > > + int len; > > > + > > > + if (!virtqueue_pop(vinput->evt, &elem)) { > > > + fprintf(stderr, "%s: virtqueue empty, dropping event\n", > > __func__); > > > + return; > > > > Looks scary. > > > > It's not different from other input devices. No buffer space -> drop > event. What else do you think should happen? We could signal "you lost > events" to the guest, but I suspect that buys us nothing. Other input > devices don't have that capability, so guests are likely not prepared to > handle the situation. For assigned device input events, how about we don't read events off the input device file if there's nowhere to put them? For things like sync that qemu generates, I suspect it's a good idea to buffer them in QEMU otherwise guest will get out of sync, right? I'm also pretty sure whoever's running the hypervisor does not want to see the fprintf. > Also, there isn't much they can actually do about > it. > > cheers, > Gerd
Hi, > > It's not different from other input devices. No buffer space -> drop > > event. What else do you think should happen? We could signal "you lost > > events" to the guest, but I suspect that buys us nothing. Other input > > devices don't have that capability, so guests are likely not prepared to > > handle the situation. > > For assigned device input events, how about we don't read events off the > input device file if there's nowhere to put them? That'll just offload the problem to the kernel. > For things like sync that qemu generates, I suspect it's a good idea > to buffer them in QEMU otherwise guest will get out of sync, right? Grouping things makes sense indeed. Mouse movements for example are reported as three events: one for the x axis, one for the y axis, and the sync. We should report either all three or none of them to the guest to avoid confusion. I'll think about how to do that best. > I'm also pretty sure whoever's running the hypervisor does not > want to see the fprintf. I'll drop it. Or maybe better make it a tracepoint. cheers, Gerd
diff --git a/hw/input/Makefile.objs b/hw/input/Makefile.objs index e8c80b9..ee8bba9 100644 --- a/hw/input/Makefile.objs +++ b/hw/input/Makefile.objs @@ -8,6 +8,10 @@ common-obj-$(CONFIG_STELLARIS_INPUT) += stellaris_input.o common-obj-$(CONFIG_TSC2005) += tsc2005.o common-obj-$(CONFIG_VMMOUSE) += vmmouse.o +ifeq ($(CONFIG_LINUX),y) +common-obj-$(CONFIG_VIRTIO) += virtio-input.o +endif + obj-$(CONFIG_MILKYMIST) += milkymist-softusb.o obj-$(CONFIG_PXA2XX) += pxa2xx_keypad.o obj-$(CONFIG_TSC210X) += tsc210x.o diff --git a/hw/input/virtio-input.c b/hw/input/virtio-input.c new file mode 100644 index 0000000..35f0cfc --- /dev/null +++ b/hw/input/virtio-input.c @@ -0,0 +1,253 @@ +/* + * 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/iov.h" + +#include "hw/qdev.h" +#include "hw/virtio/virtio.h" +#include "hw/virtio/virtio-input.h" + +#include "ui/console.h" + +#include <linux/input.h> + +/* ----------------------------------------------------------------- */ + +void virtio_input_send(VirtIOInput *vinput, virtio_input_event *event) +{ + VirtQueueElement elem; + int len; + + if (!virtqueue_pop(vinput->evt, &elem)) { + fprintf(stderr, "%s: virtqueue empty, dropping event\n", __func__); + return; + } + len = iov_from_buf(elem.in_sg, elem.in_num, + 0, event, sizeof(*event)); + virtqueue_push(vinput->evt, &elem, len); +} + +static void virtio_input_handle_evt(VirtIODevice *vdev, VirtQueue *vq) +{ + /* nothing */ +} + +static void virtio_input_handle_sts(VirtIODevice *vdev, VirtQueue *vq) +{ + VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(vdev); + VirtIOInput *vinput = VIRTIO_INPUT(vdev); + virtio_input_event event; + VirtQueueElement elem; + int len; + + while (virtqueue_pop(vinput->sts, &elem)) { + memset(&event, 0, sizeof(event)); + len = iov_to_buf(elem.out_sg, elem.out_num, + 0, &event, sizeof(event)); + if (vic->handle_status) { + vic->handle_status(vinput, &event); + } + virtqueue_push(vinput->sts, &elem, len); + } + virtio_notify(vdev, vinput->sts); +} + +static virtio_input_config *virtio_input_find_config(VirtIOInput *vinput, + uint8_t select, + uint8_t subsel) +{ + VirtIOInputConfig *cfg; + + QTAILQ_FOREACH(cfg, &vinput->cfg_list, node) { + if (select == cfg->config.select && + subsel == cfg->config.subsel) { + return &cfg->config; + } + } + return NULL; +} + +void virtio_input_add_config(VirtIOInput *vinput, + virtio_input_config *config) +{ + VirtIOInputConfig *cfg; + + if (virtio_input_find_config(vinput, config->select, config->subsel)) { + /* should not happen */ + fprintf(stderr, "%s: duplicate config: %d/%d\n", + __func__, config->select, config->subsel); + abort(); + } + + cfg = g_new0(VirtIOInputConfig, 1); + cfg->config = *config; + QTAILQ_INSERT_TAIL(&vinput->cfg_list, cfg, node); +} + +void virtio_input_init_config(VirtIOInput *vinput, + virtio_input_config *config) +{ + int i = 0; + + QTAILQ_INIT(&vinput->cfg_list); + while (config[i].select) { + virtio_input_add_config(vinput, config + i); + i++; + } +} + +void virtio_input_idstr_config(VirtIOInput *vinput, + uint8_t select, const char *string) +{ + virtio_input_config id; + + if (!string) { + return; + } + memset(&id, 0, sizeof(id)); + id.select = select; + id.size = snprintf(id.u.string, sizeof(id.u.string), "%s", string); + virtio_input_add_config(vinput, &id); +} + +static void virtio_input_get_config(VirtIODevice *vdev, uint8_t *config_data) +{ + VirtIOInput *vinput = VIRTIO_INPUT(vdev); + virtio_input_config *config; + + config = virtio_input_find_config(vinput, vinput->cfg_select, + vinput->cfg_subsel); + if (config) { + memcpy(config_data, config, vinput->cfg_size); + } else { + memset(config_data, 0, vinput->cfg_size); + } +} + +static void virtio_input_set_config(VirtIODevice *vdev, + const uint8_t *config_data) +{ + VirtIOInput *vinput = VIRTIO_INPUT(vdev); + virtio_input_config *config = (virtio_input_config *)config_data; + + vinput->cfg_select = config->select; + vinput->cfg_subsel = config->subsel; + virtio_notify_config(vdev); +} + +static uint32_t virtio_input_get_features(VirtIODevice *vdev, uint32_t f) +{ + return f; +} + +static void virtio_input_set_status(VirtIODevice *vdev, uint8_t val) +{ + VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(vdev); + VirtIOInput *vinput = VIRTIO_INPUT(vdev); + + if (val & VIRTIO_CONFIG_S_DRIVER_OK) { + if (!vinput->active) { + vinput->active = true; + if (vic->change_active) { + vic->change_active(vinput); + } + } + } +} + +static void virtio_input_reset(VirtIODevice *vdev) +{ + VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(vdev); + VirtIOInput *vinput = VIRTIO_INPUT(vdev); + + if (vinput->active) { + vinput->active = false; + if (vic->change_active) { + vic->change_active(vinput); + } + } +} + +static void virtio_input_device_realize(DeviceState *dev, Error **errp) +{ + VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(dev); + VirtIODevice *vdev = VIRTIO_DEVICE(dev); + VirtIOInput *vinput = VIRTIO_INPUT(dev); + VirtIOInputConfig *cfg; + + + if (vic->unrealize) { + vic->realize(dev, errp); + if (error_is_set(errp)) { + return; + } + } + + virtio_input_idstr_config(vinput, VIRTIO_INPUT_CFG_ID_SERIAL, + vinput->input.serial); + virtio_input_idstr_config(vinput, VIRTIO_INPUT_CFG_ID_SEAT, + vinput->input.seat); + + QTAILQ_FOREACH(cfg, &vinput->cfg_list, node) { + if (vinput->cfg_size < cfg->config.size) { + vinput->cfg_size = cfg->config.size; + } + } + vinput->cfg_size += 4; + assert(vinput->cfg_size <= sizeof(virtio_input_config)); + + virtio_init(vdev, "virtio-input", VIRTIO_ID_INPUT, + vinput->cfg_size); + vinput->evt = virtio_add_queue(vdev, 64, virtio_input_handle_evt); + vinput->sts = virtio_add_queue(vdev, 64, virtio_input_handle_sts); +} + +static void virtio_input_device_unrealize(DeviceState *dev, Error **errp) +{ + VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(dev); + VirtIODevice *vdev = VIRTIO_DEVICE(dev); + + if (vic->unrealize) { + vic->unrealize(dev, errp); + if (error_is_set(errp)) { + return; + } + } + virtio_cleanup(vdev); +} + +static void virtio_input_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); + + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); + vdc->realize = virtio_input_device_realize; + vdc->unrealize = virtio_input_device_unrealize; + vdc->get_config = virtio_input_get_config; + vdc->set_config = virtio_input_set_config; + vdc->get_features = virtio_input_get_features; + vdc->set_status = virtio_input_set_status; + vdc->reset = virtio_input_reset; +} + +static const TypeInfo virtio_input_info = { + .name = TYPE_VIRTIO_INPUT, + .parent = TYPE_VIRTIO_DEVICE, + .instance_size = sizeof(VirtIOInput), + .class_size = sizeof(VirtIOInputClass), + .class_init = virtio_input_class_init, + .abstract = true, +}; + +/* ----------------------------------------------------------------- */ + +static void virtio_register_types(void) +{ + type_register_static(&virtio_input_info); +} + +type_init(virtio_register_types) diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c index ce97514..5518192 100644 --- a/hw/virtio/virtio-pci.c +++ b/hw/virtio/virtio-pci.c @@ -23,6 +23,7 @@ #include "hw/virtio/virtio-serial.h" #include "hw/virtio/virtio-scsi.h" #include "hw/virtio/virtio-balloon.h" +#include "hw/virtio/virtio-input.h" #include "hw/pci/pci.h" #include "qemu/error-report.h" #include "hw/pci/msi.h" @@ -1531,6 +1532,40 @@ static const TypeInfo virtio_rng_pci_info = { .class_init = virtio_rng_pci_class_init, }; +/* virtio-input-pci */ + +static int virtio_input_pci_init(VirtIOPCIProxy *vpci_dev) +{ + VirtIOInputPCI *vinput = VIRTIO_INPUT_PCI(vpci_dev); + DeviceState *vdev = DEVICE(&vinput->vdev); + + qdev_set_parent_bus(vdev, BUS(&vpci_dev->bus)); + return qdev_init(vdev); +} + +static void virtio_input_pci_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VirtioPCIClass *k = VIRTIO_PCI_CLASS(klass); + PCIDeviceClass *pcidev_k = PCI_DEVICE_CLASS(klass); + + k->init = virtio_input_pci_init; + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); + + pcidev_k->vendor_id = PCI_VENDOR_ID_REDHAT_QUMRANET; + pcidev_k->device_id = PCI_DEVICE_ID_VIRTIO_INPUT; + pcidev_k->revision = VIRTIO_PCI_ABI_VERSION; + pcidev_k->class_id = PCI_CLASS_OTHERS; +} + +static const TypeInfo virtio_input_pci_info = { + .name = TYPE_VIRTIO_INPUT_PCI, + .parent = TYPE_VIRTIO_PCI, + .instance_size = sizeof(VirtIOInputPCI), + .class_init = virtio_input_pci_class_init, + .abstract = true, +}; + /* virtio-pci-bus */ static void virtio_pci_bus_new(VirtioBusState *bus, size_t bus_size, @@ -1575,6 +1610,7 @@ static const TypeInfo virtio_pci_bus_info = { static void virtio_pci_register_types(void) { type_register_static(&virtio_rng_pci_info); + type_register_static(&virtio_input_pci_info); type_register_static(&virtio_pci_bus_info); type_register_static(&virtio_pci_info); #ifdef CONFIG_VIRTFS diff --git a/hw/virtio/virtio-pci.h b/hw/virtio/virtio-pci.h index dc332ae..f1e75ad 100644 --- a/hw/virtio/virtio-pci.h +++ b/hw/virtio/virtio-pci.h @@ -24,6 +24,7 @@ #include "hw/virtio/virtio-balloon.h" #include "hw/virtio/virtio-bus.h" #include "hw/virtio/virtio-9p.h" +#include "hw/virtio/virtio-input.h" #ifdef CONFIG_VIRTFS #include "hw/9pfs/virtio-9p.h" #endif @@ -39,6 +40,7 @@ typedef struct VirtIOSerialPCI VirtIOSerialPCI; typedef struct VirtIONetPCI VirtIONetPCI; typedef struct VHostSCSIPCI VHostSCSIPCI; typedef struct VirtIORngPCI VirtIORngPCI; +typedef struct VirtIOInputPCI VirtIOInputPCI; /* virtio-pci-bus */ @@ -199,6 +201,18 @@ struct VirtIORngPCI { VirtIORNG vdev; }; +/* + * virtio-input-pci: This extends VirtioPCIProxy. + */ +#define TYPE_VIRTIO_INPUT_PCI "virtio-input-pci" +#define VIRTIO_INPUT_PCI(obj) \ + OBJECT_CHECK(VirtIOInputPCI, (obj), TYPE_VIRTIO_INPUT_PCI) + +struct VirtIOInputPCI { + VirtIOPCIProxy parent_obj; + VirtIOInput vdev; +}; + /* Virtio ABI version, if we increment this, we break the guest driver. */ #define VIRTIO_PCI_ABI_VERSION 0 diff --git a/include/hw/virtio/virtio-input.h b/include/hw/virtio/virtio-input.h new file mode 100644 index 0000000..5d37a70 --- /dev/null +++ b/include/hw/virtio/virtio-input.h @@ -0,0 +1,105 @@ +#ifndef _QEMU_VIRTIO_INPUT_H +#define _QEMU_VIRTIO_INPUT_H + +#include "ui/input.h" + +/* ----------------------------------------------------------------- */ +/* virtio input protocol */ + +/* The Virtio ID for the virtio input device */ +#define VIRTIO_ID_INPUT 18 + +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_SEAT = 0x03, + VIRTIO_INPUT_CFG_PROP_BITS = 0x10, + VIRTIO_INPUT_CFG_EV_BITS = 0x11, + VIRTIO_INPUT_CFG_ABS_INFO = 0x12, +}; + +typedef struct virtio_input_absinfo { + uint32_t min; + uint32_t max; + uint32_t fuzz; + uint32_t flat; +} virtio_input_absinfo; + +typedef struct virtio_input_config { + uint8_t select; + uint8_t subsel; + uint8_t size; + uint8_t reserved; + union { + char string[128]; + uint8_t bitmap[128]; + virtio_input_absinfo abs; + } u; +} virtio_input_config; + +typedef struct virtio_input_event { + uint16_t type; + uint16_t code; + int32_t value; +} virtio_input_event; + +/* ----------------------------------------------------------------- */ +/* qemu internals */ + +#define TYPE_VIRTIO_INPUT "virtio-input-device" +#define VIRTIO_INPUT(obj) \ + OBJECT_CHECK(VirtIOInput, (obj), TYPE_VIRTIO_INPUT) +#define VIRTIO_INPUT_GET_PARENT_CLASS(obj) \ + OBJECT_GET_PARENT_CLASS(obj, TYPE_VIRTIO_INPUT) +#define VIRTIO_INPUT_GET_CLASS(obj) \ + OBJECT_GET_CLASS(VirtIOInputClass, obj, TYPE_VIRTIO_INPUT) +#define VIRTIO_INPUT_CLASS(klass) \ + OBJECT_CLASS_CHECK(VirtIOInputClass, klass, TYPE_VIRTIO_INPUT) + +typedef struct VirtIOInput VirtIOInput; +typedef struct VirtIOInputClass VirtIOInputClass; +typedef struct VirtIOInputConfig VirtIOInputConfig; + +struct virtio_input_conf { + char *serial; + char *seat; +}; + +struct VirtIOInputConfig { + virtio_input_config config; + QTAILQ_ENTRY(VirtIOInputConfig) node; +}; + +struct VirtIOInput { + VirtIODevice parent_obj; + uint8_t cfg_select; + uint8_t cfg_subsel; + uint32_t cfg_size; + QTAILQ_HEAD(, VirtIOInputConfig) cfg_list; + VirtQueue *evt, *sts; + virtio_input_conf input; + + bool active; +}; + +struct VirtIOInputClass { + /*< private >*/ + VirtioDeviceClass parent; + /*< public >*/ + + DeviceRealize realize; + DeviceUnrealize unrealize; + void (*change_active)(VirtIOInput *vinput); + void (*handle_status)(VirtIOInput *vinput, virtio_input_event *event); +}; + +void virtio_input_send(VirtIOInput *vinput, virtio_input_event *event); +void virtio_input_init_config(VirtIOInput *vinput, + virtio_input_config *config); +void virtio_input_add_config(VirtIOInput *vinput, + virtio_input_config *config); +void virtio_input_idstr_config(VirtIOInput *vinput, + uint8_t select, const char *string); + +#endif /* _QEMU_VIRTIO_INPUT_H */ diff --git a/include/hw/virtio/virtio.h b/include/hw/virtio/virtio.h index 3e54e90..93d1607 100644 --- a/include/hw/virtio/virtio.h +++ b/include/hw/virtio/virtio.h @@ -222,6 +222,7 @@ VirtIODevice *virtio_net_init(DeviceState *dev, NICConf *conf, struct virtio_net_conf *net, uint32_t host_features); typedef struct virtio_serial_conf virtio_serial_conf; +typedef struct virtio_input_conf virtio_input_conf; typedef struct VirtIOSCSIConf VirtIOSCSIConf; typedef struct VirtIORNGConf VirtIORNGConf;
This patch adds virtio-input support to qemu. It brings a abstract base class providing core support, other classes can build on it to actually implement input devices. virtio-input basically sends linux input layer events (evdev) over virtio. Signed-off-by: Gerd Hoffmann <kraxel@redhat.com> --- hw/input/Makefile.objs | 4 + hw/input/virtio-input.c | 253 +++++++++++++++++++++++++++++++++++++++ hw/virtio/virtio-pci.c | 36 ++++++ hw/virtio/virtio-pci.h | 14 +++ include/hw/virtio/virtio-input.h | 105 ++++++++++++++++ include/hw/virtio/virtio.h | 1 + 6 files changed, 413 insertions(+) create mode 100644 hw/input/virtio-input.c create mode 100644 include/hw/virtio/virtio-input.h