diff mbox

[5/5,RfC] virtio-input

Message ID 1395153687-11030-6-git-send-email-kraxel@redhat.com
State New
Headers show

Commit Message

Gerd Hoffmann March 18, 2014, 2:41 p.m. UTC
Experimental virtio protocol implementation for input devices.

See docs/specs/virtio-input.txt (+code) for more info.
Detailed specs are to be written.

Guest bits: http://www.kraxel.org/cgit/linux/log/?h=virtio-input

Config isn't exactly small, so I tend to make that a pure virtio 1.0
device where virtio config space can live in mmio and doesn't waste
io address space.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
 docs/specs/virtio-input.txt      |  50 ++++
 hw/input/Makefile.objs           |   4 +
 hw/input/virtio-input.c          | 559 +++++++++++++++++++++++++++++++++++++++
 hw/virtio/virtio-pci.c           |  88 ++++++
 hw/virtio/virtio-pci.h           |  18 ++
 include/hw/pci/pci.h             |   1 +
 include/hw/virtio/virtio-input.h |  71 +++++
 7 files changed, 791 insertions(+)
 create mode 100644 docs/specs/virtio-input.txt
 create mode 100644 hw/input/virtio-input.c
 create mode 100644 include/hw/virtio/virtio-input.h
diff mbox

Patch

diff --git a/docs/specs/virtio-input.txt b/docs/specs/virtio-input.txt
new file mode 100644
index 0000000..eaeb281
--- /dev/null
+++ b/docs/specs/virtio-input.txt
@@ -0,0 +1,50 @@ 
+
+virtio-input protocol
+=====================
+
+TL;DR  It is basically the linux input layer event API in virtio.
+ioctls to query device capabilities map to config space.  Events
+travel via virtqueues.
+
+
+config space
+------------
+
+Device config space (see include/hw/virtio/virtio-input.h, struct
+virtio_input_config) has a four byte header.
+
+ * First byte (select) can be written by the guest to pick the piece of
+   information it wants query.
+
+ * Third byte (size) is read-only and returns the size of the
+   information.  If there isn't any (for example when checking mouse
+   axis information on a virtual keyboard) size will be zero.
+
+ * Second and fourth byte are reserved and should not be written by the
+   guest.
+
+
+After the header the actual information is stored:
+
+VIRTIO_INPUT_CFG_ID_NAME    --  u.id_name
+   name of the device
+
+VIRTIO_INPUT_CFG_FL_REPEAT  --  u.flag
+   true if autorepeat should be enabled for the device
+
+VIRTIO_INPUT_CFG_EV_*       --  u.ev_bits
+   bitmap for the supported event codes
+
+VIRTIO_INPUT_CFG_ABS_BASE   --  u.abs
+   struct virtio_input_absinfo for each absolute axis.
+   select = VIRTIO_INPUT_CFG_ABS_BASE + ABS_$axis
+
+
+virtqueues
+==========
+
+One queue (#0) for input events (KEY, ABS, REL + friends).
+One queue (#1) for status reports (LED).
+
+Both carry virtio_input_event structs.
+#0 is host->guest,  #1 is guest->host.
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..51a8676
--- /dev/null
+++ b/hw/input/virtio-input.c
@@ -0,0 +1,559 @@ 
+/*
+ * 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 <linux/input.h>
+
+#define VIRTIO_ID_NAME_KEYBOARD "QEMU Virtio Keyboard"
+#define VIRTIO_ID_NAME_MOUSE    "QEMU Virtio Mouse"
+#define VIRTIO_ID_NAME_TABLET   "QEMU Virtio Tablet"
+
+/* ----------------------------------------------------------------- */
+
+static const unsigned int keymap_qcode[Q_KEY_CODE_MAX] = {
+    [Q_KEY_CODE_ESC]                 = KEY_ESC,
+    [Q_KEY_CODE_1]                   = KEY_1,
+    [Q_KEY_CODE_2]                   = KEY_2,
+    [Q_KEY_CODE_3]                   = KEY_3,
+    [Q_KEY_CODE_4]                   = KEY_4,
+    [Q_KEY_CODE_5]                   = KEY_5,
+    [Q_KEY_CODE_6]                   = KEY_6,
+    [Q_KEY_CODE_7]                   = KEY_7,
+    [Q_KEY_CODE_8]                   = KEY_8,
+    [Q_KEY_CODE_9]                   = KEY_9,
+    [Q_KEY_CODE_0]                   = KEY_0,
+    [Q_KEY_CODE_MINUS]               = KEY_MINUS,
+    [Q_KEY_CODE_EQUAL]               = KEY_EQUAL,
+    [Q_KEY_CODE_BACKSPACE]           = KEY_BACKSPACE,
+
+    [Q_KEY_CODE_TAB]                 = KEY_TAB,
+    [Q_KEY_CODE_Q]                   = KEY_Q,
+    [Q_KEY_CODE_W]                   = KEY_W,
+    [Q_KEY_CODE_E]                   = KEY_E,
+    [Q_KEY_CODE_R]                   = KEY_R,
+    [Q_KEY_CODE_T]                   = KEY_T,
+    [Q_KEY_CODE_Y]                   = KEY_Y,
+    [Q_KEY_CODE_U]                   = KEY_U,
+    [Q_KEY_CODE_I]                   = KEY_I,
+    [Q_KEY_CODE_O]                   = KEY_O,
+    [Q_KEY_CODE_P]                   = KEY_P,
+    [Q_KEY_CODE_BRACKET_LEFT]        = KEY_LEFTBRACE,
+    [Q_KEY_CODE_BRACKET_RIGHT]       = KEY_RIGHTBRACE,
+    [Q_KEY_CODE_RET]                 = KEY_ENTER,
+
+    [Q_KEY_CODE_CTRL]                = KEY_LEFTCTRL,
+    [Q_KEY_CODE_A]                   = KEY_A,
+    [Q_KEY_CODE_S]                   = KEY_S,
+    [Q_KEY_CODE_D]                   = KEY_D,
+    [Q_KEY_CODE_F]                   = KEY_F,
+    [Q_KEY_CODE_G]                   = KEY_G,
+    [Q_KEY_CODE_H]                   = KEY_H,
+    [Q_KEY_CODE_J]                   = KEY_J,
+    [Q_KEY_CODE_K]                   = KEY_K,
+    [Q_KEY_CODE_L]                   = KEY_L,
+    [Q_KEY_CODE_SEMICOLON]           = KEY_SEMICOLON,
+    [Q_KEY_CODE_APOSTROPHE]          = KEY_APOSTROPHE,
+    [Q_KEY_CODE_GRAVE_ACCENT]        = KEY_GRAVE,
+
+    [Q_KEY_CODE_SHIFT]               = KEY_LEFTSHIFT,
+    [Q_KEY_CODE_BACKSLASH]           = KEY_BACKSLASH,
+    [Q_KEY_CODE_LESS]                = KEY_102ND,
+    [Q_KEY_CODE_Z]                   = KEY_Z,
+    [Q_KEY_CODE_X]                   = KEY_X,
+    [Q_KEY_CODE_C]                   = KEY_C,
+    [Q_KEY_CODE_V]                   = KEY_V,
+    [Q_KEY_CODE_B]                   = KEY_B,
+    [Q_KEY_CODE_N]                   = KEY_N,
+    [Q_KEY_CODE_M]                   = KEY_M,
+    [Q_KEY_CODE_COMMA]               = KEY_COMMA,
+    [Q_KEY_CODE_DOT]                 = KEY_DOT,
+    [Q_KEY_CODE_SLASH]               = KEY_SLASH,
+    [Q_KEY_CODE_SHIFT_R]             = KEY_RIGHTSHIFT,
+
+    [Q_KEY_CODE_ALT]                 = KEY_LEFTALT,
+    [Q_KEY_CODE_SPC]                 = KEY_SPACE,
+    [Q_KEY_CODE_CAPS_LOCK]           = KEY_CAPSLOCK,
+
+    [Q_KEY_CODE_F1]                  = KEY_F1,
+    [Q_KEY_CODE_F2]                  = KEY_F2,
+    [Q_KEY_CODE_F3]                  = KEY_F3,
+    [Q_KEY_CODE_F4]                  = KEY_F4,
+    [Q_KEY_CODE_F5]                  = KEY_F5,
+    [Q_KEY_CODE_F6]                  = KEY_F6,
+    [Q_KEY_CODE_F7]                  = KEY_F7,
+    [Q_KEY_CODE_F8]                  = KEY_F8,
+    [Q_KEY_CODE_F9]                  = KEY_F9,
+    [Q_KEY_CODE_F10]                 = KEY_F10,
+    [Q_KEY_CODE_NUM_LOCK]            = KEY_NUMLOCK,
+    [Q_KEY_CODE_SCROLL_LOCK]         = KEY_SCROLLLOCK,
+
+    [Q_KEY_CODE_KP_0]                = KEY_KP0,
+    [Q_KEY_CODE_KP_1]                = KEY_KP1,
+    [Q_KEY_CODE_KP_2]                = KEY_KP2,
+    [Q_KEY_CODE_KP_3]                = KEY_KP3,
+    [Q_KEY_CODE_KP_4]                = KEY_KP4,
+    [Q_KEY_CODE_KP_5]                = KEY_KP5,
+    [Q_KEY_CODE_KP_6]                = KEY_KP6,
+    [Q_KEY_CODE_KP_7]                = KEY_KP7,
+    [Q_KEY_CODE_KP_8]                = KEY_KP8,
+    [Q_KEY_CODE_KP_9]                = KEY_KP9,
+    [Q_KEY_CODE_KP_SUBTRACT]         = KEY_KPMINUS,
+    [Q_KEY_CODE_KP_ADD]              = KEY_KPPLUS,
+    [Q_KEY_CODE_KP_DECIMAL]          = KEY_KPDOT,
+    [Q_KEY_CODE_KP_ENTER]            = KEY_KPENTER,
+    [Q_KEY_CODE_KP_DIVIDE]           = KEY_KPSLASH,
+    [Q_KEY_CODE_KP_MULTIPLY]         = KEY_KPASTERISK,
+
+    [Q_KEY_CODE_F11]                 = KEY_F11,
+    [Q_KEY_CODE_F12]                 = KEY_F12,
+
+    [Q_KEY_CODE_CTRL_R]              = KEY_RIGHTCTRL,
+    [Q_KEY_CODE_SYSRQ]               = KEY_SYSRQ,
+    [Q_KEY_CODE_ALT_R]               = KEY_RIGHTALT,
+
+    [Q_KEY_CODE_HOME]                = KEY_HOME,
+    [Q_KEY_CODE_UP]                  = KEY_UP,
+    [Q_KEY_CODE_PGUP]                = KEY_PAGEUP,
+    [Q_KEY_CODE_LEFT]                = KEY_LEFT,
+    [Q_KEY_CODE_RIGHT]               = KEY_RIGHT,
+    [Q_KEY_CODE_END]                 = KEY_END,
+    [Q_KEY_CODE_DOWN]                = KEY_DOWN,
+    [Q_KEY_CODE_PGDN]                = KEY_PAGEDOWN,
+    [Q_KEY_CODE_INSERT]              = KEY_INSERT,
+    [Q_KEY_CODE_DELETE]              = KEY_DELETE,
+
+    [Q_KEY_CODE_META_L]              = KEY_LEFTMETA,
+    [Q_KEY_CODE_META_R]              = KEY_RIGHTMETA,
+};
+
+static const unsigned int keymap_button[INPUT_BUTTON_MAX] = {
+    [INPUT_BUTTON_LEFT]              = BTN_LEFT,
+    [INPUT_BUTTON_RIGHT]             = BTN_RIGHT,
+    [INPUT_BUTTON_MIDDLE]            = BTN_MIDDLE,
+    [INPUT_BUTTON_WHEEL_UP]          = BTN_GEAR_UP,
+    [INPUT_BUTTON_WHEEL_DOWN]        = BTN_GEAR_DOWN,
+};
+
+static const unsigned int axismap_rel[INPUT_AXIS_MAX] = {
+    [INPUT_AXIS_X]                   = REL_X,
+    [INPUT_AXIS_Y]                   = REL_Y,
+};
+
+static const unsigned int axismap_abs[INPUT_AXIS_MAX] = {
+    [INPUT_AXIS_X]                   = ABS_X,
+    [INPUT_AXIS_Y]                   = ABS_Y,
+};
+
+/* ----------------------------------------------------------------- */
+
+static 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_event(DeviceState *dev, QemuConsole *src,
+                                      InputEvent *evt)
+{
+    VirtIOInput *vinput = VIRTIO_INPUT(dev);
+    virtio_input_event event;
+    int qcode;
+
+    switch (evt->kind) {
+    case INPUT_EVENT_KIND_KEY:
+        qcode = qemu_input_key_value_to_qcode(evt->key->key);
+        if (qcode && keymap_qcode[qcode]) {
+            event.type  = cpu_to_le16(EV_KEY);
+            event.code  = cpu_to_le16(keymap_qcode[qcode]);
+            event.value = cpu_to_le32(evt->key->down ? 1 : 0);
+            virtio_input_send(vinput, &event);
+        } else {
+            if (evt->key->down) {
+                fprintf(stderr, "%s: unmapped key: %d [%s]\n", __func__,
+                        qcode, QKeyCode_lookup[qcode]);
+            }
+        }
+        break;
+    case INPUT_EVENT_KIND_BTN:
+        if (keymap_button[evt->btn->button]) {
+            event.type  = cpu_to_le16(EV_KEY);
+            event.code  = cpu_to_le16(keymap_button[evt->btn->button]);
+            event.value = cpu_to_le32(evt->btn->down ? 1 : 0);
+            virtio_input_send(vinput, &event);
+        } else {
+            if (evt->btn->down) {
+                fprintf(stderr, "%s: unmapped button: %d [%s]\n", __func__,
+                        evt->btn->button, InputButton_lookup[evt->btn->button]);
+            }
+        }
+        break;
+    case INPUT_EVENT_KIND_REL:
+        event.type  = cpu_to_le16(EV_REL);
+        event.code  = cpu_to_le16(axismap_rel[evt->rel->axis]);
+        event.value = cpu_to_le32(evt->rel->value);
+        virtio_input_send(vinput, &event);
+        break;
+    case INPUT_EVENT_KIND_ABS:
+        event.type  = cpu_to_le16(EV_ABS);
+        event.code  = cpu_to_le16(axismap_abs[evt->abs->axis]);
+        event.value = cpu_to_le32(evt->abs->value);
+        virtio_input_send(vinput, &event);
+        break;
+    default:
+        /* keep gcc happy */
+        break;
+    }
+}
+
+static void virtio_input_handle_sync(DeviceState *dev)
+{
+    VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+    VirtIOInput *vinput = VIRTIO_INPUT(dev);
+    virtio_input_event event = {
+        .type  = cpu_to_le16(EV_SYN),
+        .code  = cpu_to_le16(SYN_REPORT),
+        .value = 0,
+    };
+
+    virtio_input_send(vinput, &event);
+    virtio_notify(vdev, vinput->evt);
+}
+
+static void virtio_input_handle_evt(VirtIODevice *vdev, VirtQueue *vq)
+{
+    /* nothing */
+}
+
+static void virtio_input_handle_sts(VirtIODevice *vdev, VirtQueue *vq)
+{
+    VirtIOInput *vinput = VIRTIO_INPUT(vdev);
+    virtio_input_event event;
+    VirtQueueElement elem;
+    int len;
+
+    fprintf(stderr, "%s:\n", __func__);
+    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));
+        switch (le16_to_cpu(event.type)) {
+        default:
+            fprintf(stderr, "%s: unknown type %d\n", __func__,
+                    le16_to_cpu(event.type));
+            break;
+        }
+        virtqueue_push(vinput->sts, &elem, len);
+    }
+    virtio_notify(vdev, vinput->sts);
+}
+
+static virtio_input_config *virtio_input_find_config(VirtIOInput *vinput,
+                                                     uint16_t select)
+{
+    int i = 0;
+
+    while (vinput->config && vinput->config[i].select) {
+        if (vinput->config[i].select == select) {
+            return &vinput->config[i];
+        }
+        i++;
+    }
+    return NULL;
+}
+
+static void virtio_input_key_config(VirtIOInput *vinput,
+                                    const unsigned int *keymap,
+                                    size_t mapsize)
+{
+    virtio_input_config *keys;
+    int i, bit, byte, bmax = 0;
+
+    keys = virtio_input_find_config(vinput, VIRTIO_INPUT_CFG_EV_KEY);
+    assert(keys != NULL);
+    for (i = 0; i < mapsize; i++) {
+        bit = keymap[i];
+        if (!bit) {
+            continue;
+        }
+        byte = bit / 8;
+        bit  = bit % 8;
+        keys->u.ev_bits[byte] |= (1 << bit);
+        if (bmax < byte+1) {
+            bmax = byte+1;
+        }
+    }
+    keys->size = bmax;
+}
+
+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);
+    if (config) {
+        memcpy(config_data, config, sizeof(*config));
+    } else {
+        memset(config_data, 0, sizeof(*config));
+    }
+}
+
+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;
+    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)
+{
+    VirtIOInput *vinput = VIRTIO_INPUT(vdev);
+
+    if (val & VIRTIO_CONFIG_S_DRIVER_OK) {
+        if (!vinput->active) {
+            qemu_input_handler_activate(vinput->hs);
+            vinput->active = true;
+        }
+    }
+}
+
+static void virtio_input_reset(VirtIODevice *vdev)
+{
+    VirtIOInput *vinput = VIRTIO_INPUT(vdev);
+
+    if (vinput->active) {
+        qemu_input_handler_deactivate(vinput->hs);
+        vinput->active = false;
+    }
+}
+
+static void virtio_input_device_realize(DeviceState *dev, Error **errp)
+{
+    VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+    VirtIOInput *vinput = VIRTIO_INPUT(dev);
+
+    virtio_init(vdev, "virtio-input", VIRTIO_ID_INPUT,
+                sizeof(virtio_input_config));
+    vinput->evt = virtio_add_queue(vdev, 64, virtio_input_handle_evt);
+    vinput->sts = virtio_add_queue(vdev, 64, virtio_input_handle_sts);
+    vinput->hs = qemu_input_handler_register(dev, vinput->handler);
+}
+
+static void virtio_input_device_unrealize(DeviceState *dev, Error **errp)
+{
+    VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+    VirtIOInput *vinput = VIRTIO_INPUT(dev);
+
+    qemu_input_handler_unregister(vinput->hs);
+    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_init    = virtio_input_class_init,
+    .abstract      = true,
+};
+
+/* ----------------------------------------------------------------- */
+
+static QemuInputHandler virtio_keyboard_handler = {
+    .name  = VIRTIO_ID_NAME_KEYBOARD,
+    .mask  = INPUT_EVENT_MASK_KEY,
+    .event = virtio_input_handle_event,
+    .sync  = virtio_input_handle_sync,
+};
+
+static struct virtio_input_config virtio_keyboard_config[] = {
+    {
+        .select    = VIRTIO_INPUT_CFG_ID_NAME,
+        .size      = sizeof(VIRTIO_ID_NAME_KEYBOARD),
+        .u.id_name = VIRTIO_ID_NAME_KEYBOARD,
+    },{
+        .select    = VIRTIO_INPUT_CFG_FL_REPEAT,
+        .size      = 1,
+        .u.flag    = true,
+    },{
+        .select    = VIRTIO_INPUT_CFG_EV_KEY,
+    },{
+        .select    = VIRTIO_INPUT_CFG_EV_LED,
+        .size      = 1,
+        .u.ev_bits = {
+            (1 << LED_NUML) | (1 << LED_CAPSL) | (1 << LED_SCROLLL),
+        },
+    },
+    { /* end of list */ },
+};
+
+static void virtio_keyboard_init(Object *obj)
+{
+    VirtIOInput *vinput = VIRTIO_INPUT(obj);
+
+    vinput->config  = virtio_keyboard_config;
+    vinput->handler = &virtio_keyboard_handler;
+    virtio_input_key_config(vinput, keymap_qcode,
+                            ARRAY_SIZE(keymap_qcode));
+}
+
+static const TypeInfo virtio_keyboard_info = {
+    .name          = TYPE_VIRTIO_KEYBOARD,
+    .parent        = TYPE_VIRTIO_INPUT,
+    .instance_size = sizeof(VirtIOInput),
+    .instance_init = virtio_keyboard_init,
+};
+
+/* ----------------------------------------------------------------- */
+
+static QemuInputHandler virtio_mouse_handler = {
+    .name  = VIRTIO_ID_NAME_MOUSE,
+    .mask  = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_REL,
+    .event = virtio_input_handle_event,
+    .sync  = virtio_input_handle_sync,
+};
+
+static struct virtio_input_config virtio_mouse_config[] = {
+    {
+        .select    = VIRTIO_INPUT_CFG_ID_NAME,
+        .size      = sizeof(VIRTIO_ID_NAME_MOUSE),
+        .u.id_name = VIRTIO_ID_NAME_MOUSE,
+    },{
+        .select    = VIRTIO_INPUT_CFG_EV_KEY,
+    },{
+        .select    = VIRTIO_INPUT_CFG_EV_REL,
+        .size      = 1,
+        .u.ev_bits = {
+            (1 << REL_X) | (1 << REL_Y),
+        },
+    },
+    { /* end of list */ },
+};
+
+static void virtio_mouse_init(Object *obj)
+{
+    VirtIOInput *vinput = VIRTIO_INPUT(obj);
+
+    vinput->config  = virtio_mouse_config;
+    vinput->handler = &virtio_mouse_handler;
+    virtio_input_key_config(vinput, keymap_button,
+                            ARRAY_SIZE(keymap_button));
+}
+
+static const TypeInfo virtio_mouse_info = {
+    .name          = TYPE_VIRTIO_MOUSE,
+    .parent        = TYPE_VIRTIO_INPUT,
+    .instance_size = sizeof(VirtIOInput),
+    .instance_init = virtio_mouse_init,
+};
+
+/* ----------------------------------------------------------------- */
+
+static QemuInputHandler virtio_tablet_handler = {
+    .name  = VIRTIO_ID_NAME_TABLET,
+    .mask  = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_ABS,
+    .event = virtio_input_handle_event,
+    .sync  = virtio_input_handle_sync,
+};
+
+static struct virtio_input_config virtio_tablet_config[] = {
+    {
+        .select    = VIRTIO_INPUT_CFG_ID_NAME,
+        .size      = sizeof(VIRTIO_ID_NAME_TABLET),
+        .u.id_name = VIRTIO_ID_NAME_TABLET,
+    },{
+        .select    = VIRTIO_INPUT_CFG_EV_KEY,
+    },{
+        .select    = VIRTIO_INPUT_CFG_EV_ABS,
+        .size      = 1,
+        .u.ev_bits = {
+            (1 << ABS_X) | (1 << ABS_Y),
+        },
+    },{
+        .select    = VIRTIO_INPUT_CFG_ABS_BASE + ABS_X,
+        .size      = sizeof(virtio_input_absinfo),
+#if 0
+        /* FIXME */
+        .u.abs.max = cpu_to_le32(INPUT_EVENT_ABS_SIZE),
+#else
+        .u.abs.max = INPUT_EVENT_ABS_SIZE,
+#endif
+    },{
+        .select    = VIRTIO_INPUT_CFG_ABS_BASE + ABS_Y,
+        .size      = sizeof(virtio_input_absinfo),
+#if 0
+        /* FIXME */
+        .u.abs.max = cpu_to_le32(INPUT_EVENT_ABS_SIZE),
+#else
+        .u.abs.max = INPUT_EVENT_ABS_SIZE,
+#endif
+    },
+    { /* end of list */ },
+};
+
+static void virtio_tablet_init(Object *obj)
+{
+    VirtIOInput *vinput = VIRTIO_INPUT(obj);
+
+    vinput->config  = virtio_tablet_config;
+    vinput->handler = &virtio_tablet_handler;
+    virtio_input_key_config(vinput, keymap_button,
+                            ARRAY_SIZE(keymap_button));
+}
+
+static const TypeInfo virtio_tablet_info = {
+    .name          = TYPE_VIRTIO_TABLET,
+    .parent        = TYPE_VIRTIO_INPUT,
+    .instance_size = sizeof(VirtIOInput),
+    .instance_init = virtio_tablet_init,
+};
+
+/* ----------------------------------------------------------------- */
+
+static void virtio_register_types(void)
+{
+    type_register_static(&virtio_input_info);
+    type_register_static(&virtio_keyboard_info);
+    type_register_static(&virtio_mouse_info);
+    type_register_static(&virtio_tablet_info);
+}
+
+type_init(virtio_register_types)
diff --git a/hw/virtio/virtio-pci.c b/hw/virtio/virtio-pci.c
index 7b91841..56405dc 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"
@@ -1529,6 +1530,89 @@  static const TypeInfo virtio_rng_pci_info = {
     .class_init    = virtio_rng_pci_class_init,
 };
 
+/* virtio-input-pci */
+
+static Property virtio_input_pci_properties[] = {
+    DEFINE_VIRTIO_COMMON_FEATURES(VirtIOPCIProxy, host_features),
+    DEFINE_PROP_UINT32("vectors", VirtIOPCIProxy, nvectors, 2),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+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);
+    dc->props = virtio_input_pci_properties;
+
+    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 void virtio_keyboard_initfn(Object *obj)
+{
+    VirtIOInputPCI *dev = VIRTIO_INPUT_PCI(obj);
+    object_initialize(&dev->vdev, sizeof(dev->vdev), TYPE_VIRTIO_KEYBOARD);
+    object_property_add_child(obj, "virtio-backend", OBJECT(&dev->vdev), NULL);
+}
+
+static void virtio_mouse_initfn(Object *obj)
+{
+    VirtIOInputPCI *dev = VIRTIO_INPUT_PCI(obj);
+    object_initialize(&dev->vdev, sizeof(dev->vdev), TYPE_VIRTIO_MOUSE);
+    object_property_add_child(obj, "virtio-backend", OBJECT(&dev->vdev), NULL);
+}
+
+static void virtio_tablet_initfn(Object *obj)
+{
+    VirtIOInputPCI *dev = VIRTIO_INPUT_PCI(obj);
+    object_initialize(&dev->vdev, sizeof(dev->vdev), TYPE_VIRTIO_TABLET);
+    object_property_add_child(obj, "virtio-backend", OBJECT(&dev->vdev), NULL);
+}
+
+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,
+};
+
+static const TypeInfo virtio_keyboard_pci_info = {
+    .name          = TYPE_VIRTIO_KEYBOARD_PCI,
+    .parent        = TYPE_VIRTIO_INPUT_PCI,
+    .instance_size = sizeof(VirtIOInputPCI),
+    .instance_init = virtio_keyboard_initfn,
+};
+
+static const TypeInfo virtio_mouse_pci_info = {
+    .name          = TYPE_VIRTIO_MOUSE_PCI,
+    .parent        = TYPE_VIRTIO_INPUT_PCI,
+    .instance_size = sizeof(VirtIOInputPCI),
+    .instance_init = virtio_mouse_initfn,
+};
+
+static const TypeInfo virtio_tablet_pci_info = {
+    .name          = TYPE_VIRTIO_TABLET_PCI,
+    .parent        = TYPE_VIRTIO_INPUT_PCI,
+    .instance_size = sizeof(VirtIOInputPCI),
+    .instance_init = virtio_tablet_initfn,
+};
+
 /* virtio-pci-bus */
 
 static void virtio_pci_bus_new(VirtioBusState *bus, size_t bus_size,
@@ -1573,6 +1657,10 @@  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_keyboard_pci_info);
+    type_register_static(&virtio_mouse_pci_info);
+    type_register_static(&virtio_tablet_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..77f2843 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,22 @@  struct VirtIORngPCI {
     VirtIORNG vdev;
 };
 
+/*
+ * virtio-keyboard-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)
+
+#define TYPE_VIRTIO_KEYBOARD_PCI  "virtio-keyboard-pci"
+#define TYPE_VIRTIO_MOUSE_PCI     "virtio-mouse-pci"
+#define TYPE_VIRTIO_TABLET_PCI    "virtio-tablet-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/pci/pci.h b/include/hw/pci/pci.h
index 693dd6b..94a6928 100644
--- a/include/hw/pci/pci.h
+++ b/include/hw/pci/pci.h
@@ -80,6 +80,7 @@ 
 #define PCI_DEVICE_ID_VIRTIO_SCSI        0x1004
 #define PCI_DEVICE_ID_VIRTIO_RNG         0x1005
 #define PCI_DEVICE_ID_VIRTIO_9P          0x1009
+#define PCI_DEVICE_ID_VIRTIO_INPUT       0x1021
 
 #define PCI_VENDOR_ID_REDHAT             0x1b36
 #define PCI_DEVICE_ID_REDHAT_BRIDGE      0x0001
diff --git a/include/hw/virtio/virtio-input.h b/include/hw/virtio/virtio-input.h
new file mode 100644
index 0000000..605f1e9
--- /dev/null
+++ b/include/hw/virtio/virtio-input.h
@@ -0,0 +1,71 @@ 
+#ifndef _QEMU_VIRTIO_INPUT_H
+#define _QEMU_VIRTIO_INPUT_H
+
+#include "ui/input.h"
+
+#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 TYPE_VIRTIO_KEYBOARD  "virtio-keyboard"
+#define TYPE_VIRTIO_MOUSE     "virtio-mouse"
+#define TYPE_VIRTIO_TABLET    "virtio-tablet"
+
+/* The Virtio ID for the virtio input device */
+#define VIRTIO_ID_INPUT 0x21
+
+enum virtio_input_config_select {
+    VIRTIO_INPUT_CFG_UNSET     = 0x00,
+    VIRTIO_INPUT_CFG_ID_NAME   = 0x01,
+    VIRTIO_INPUT_CFG_FL_REPEAT = 0x08,
+
+    VIRTIO_INPUT_CFG_EV_KEY    = 0x10,
+    VIRTIO_INPUT_CFG_EV_REL,
+    VIRTIO_INPUT_CFG_EV_ABS,
+    VIRTIO_INPUT_CFG_EV_MSC,
+    VIRTIO_INPUT_CFG_EV_SW,
+
+    VIRTIO_INPUT_CFG_EV_LED    = 0x20,
+
+    VIRTIO_INPUT_CFG_ABS_BASE  = 0x30,
+};
+
+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        reserved1;
+    uint8_t        size;
+    uint8_t        reserved2;
+    union {
+        char       id_name[128];
+        uint8_t    flag;
+        uint8_t    ev_bits[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;
+
+typedef struct VirtIOInput {
+    VirtIODevice           parent_obj;
+    uint8_t                cfg_select;
+    virtio_input_config    *config;
+    VirtQueue              *evt, *sts;
+    QemuInputHandler       *handler;
+    QemuInputHandlerState  *hs;
+    bool                   active;
+} VirtIOInput;
+
+#endif /* _QEMU_VIRTIO_INPUT_H */