Patchwork [qemu,3/6] virtio-input: core code & base class

login
register
mail settings
Submitter Gerd Hoffmann
Date April 10, 2014, 9:07 a.m.
Message ID <1397120874-17166-6-git-send-email-kraxel@redhat.com>
Download mbox | patch
Permalink /patch/338067/
State New
Headers show

Comments

Gerd Hoffmann - April 10, 2014, 9:07 a.m.
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
Michael S. Tsirkin - April 10, 2014, 11:06 a.m.
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
Gerd Hoffmann - April 10, 2014, 12:22 p.m.
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
Michael S. Tsirkin - April 10, 2014, 2:56 p.m.
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
Gerd Hoffmann - April 11, 2014, 7:17 a.m.
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

Patch

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;