diff --git a/hw/input/virtio-input-hid.c b/hw/input/virtio-input-hid.c
index 5d12157114..3ee0c1814a 100644
--- a/hw/input/virtio-input-hid.c
+++ b/hw/input/virtio-input-hid.c
@@ -121,6 +121,8 @@ static const unsigned int keymap_qcode[Q_KEY_CODE__MAX] = {
 
     [Q_KEY_CODE_CTRL_R]              = KEY_RIGHTCTRL,
     [Q_KEY_CODE_SYSRQ]               = KEY_SYSRQ,
+    [Q_KEY_CODE_PRINT]               = KEY_SYSRQ,
+    [Q_KEY_CODE_PAUSE]               = KEY_PAUSE,
     [Q_KEY_CODE_ALT_R]               = KEY_RIGHTALT,
 
     [Q_KEY_CODE_HOME]                = KEY_HOME,
@@ -482,12 +484,12 @@ static struct virtio_input_config virtio_tablet_config[] = {
         .select    = VIRTIO_INPUT_CFG_ABS_INFO,
         .subsel    = ABS_X,
         .size      = sizeof(virtio_input_absinfo),
-        .u.abs.max = const_le32(INPUT_EVENT_ABS_SIZE),
+        .u.abs.max = const_le32(INPUT_EVENT_ABS_SIZE - 1),
     },{
         .select    = VIRTIO_INPUT_CFG_ABS_INFO,
         .subsel    = ABS_Y,
         .size      = sizeof(virtio_input_absinfo),
-        .u.abs.max = const_le32(INPUT_EVENT_ABS_SIZE),
+        .u.abs.max = const_le32(INPUT_EVENT_ABS_SIZE - 1),
     },
     { /* end of list */ },
 };
diff --git a/hw/input/virtio-input-host.c b/hw/input/virtio-input-host.c
index 9e0f46d88f..cb79e80024 100644
--- a/hw/input/virtio-input-host.c
+++ b/hw/input/virtio-input-host.c
@@ -70,13 +70,39 @@ static void virtio_input_bits_config(VirtIOInputHost *vih,
     virtio_input_add_config(VIRTIO_INPUT(vih), &bits);
 }
 
+static void virtio_input_abs_config(VirtIOInputHost *vih, int axis)
+{
+    virtio_input_config config;
+    struct input_absinfo absinfo;
+    int rc;
+
+    rc = ioctl(vih->fd, EVIOCGABS(axis), &absinfo);
+    if (rc < 0) {
+        return;
+    }
+
+    memset(&config, 0, sizeof(config));
+    config.select = VIRTIO_INPUT_CFG_ABS_INFO;
+    config.subsel = axis;
+    config.size   = sizeof(virtio_input_absinfo);
+
+    config.u.abs.min  = cpu_to_le32(absinfo.minimum);
+    config.u.abs.max  = cpu_to_le32(absinfo.maximum);
+    config.u.abs.fuzz = cpu_to_le32(absinfo.fuzz);
+    config.u.abs.flat = cpu_to_le32(absinfo.flat);
+    config.u.abs.res  = cpu_to_le32(absinfo.resolution);
+
+    virtio_input_add_config(VIRTIO_INPUT(vih), &config);
+}
+
 static void virtio_input_host_realize(DeviceState *dev, Error **errp)
 {
     VirtIOInputHost *vih = VIRTIO_INPUT_HOST(dev);
     VirtIOInput *vinput = VIRTIO_INPUT(dev);
-    virtio_input_config id;
+    virtio_input_config id, *abs;
     struct input_id ids;
-    int rc, ver;
+    int rc, ver, i, axis;
+    uint8_t byte;
 
     if (!vih->evdev) {
         error_setg(errp, "evdev property is required");
@@ -125,6 +151,23 @@ static void virtio_input_host_realize(DeviceState *dev, Error **errp)
     virtio_input_bits_config(vih, EV_ABS, ABS_CNT);
     virtio_input_bits_config(vih, EV_MSC, MSC_CNT);
     virtio_input_bits_config(vih, EV_SW,  SW_CNT);
+    virtio_input_bits_config(vih, EV_LED, LED_CNT);
+
+    abs = virtio_input_find_config(VIRTIO_INPUT(vih),
+        VIRTIO_INPUT_CFG_EV_BITS, EV_ABS);
+    if (abs) {
+        for (i = 0; i < abs->size; i++) {
+            byte = abs->u.bitmap[i];
+            axis = 8 * i;
+            while (byte) {
+                if (byte & 1) {
+                    virtio_input_abs_config(vih, axis);
+                }
+                axis++;
+                byte >>= 1;
+            }
+        }
+    }
 
     qemu_set_fd_handler(vih->fd, virtio_input_host_event, NULL, vih);
     return;
@@ -145,6 +188,28 @@ static void virtio_input_host_unrealize(DeviceState *dev, Error **errp)
     }
 }
 
+static void virtio_input_host_handle_status(VirtIOInput *vinput,
+                                            virtio_input_event *event)
+{
+    VirtIOInputHost *vih = VIRTIO_INPUT_HOST(vinput);
+    struct input_event evdev;
+    int rc;
+
+    if (gettimeofday(&evdev.time, NULL)) {
+        perror("virtio_input_host_handle_status: gettimeofday");
+        return;
+    }
+
+    evdev.type = le16_to_cpu(event->type);
+    evdev.code = le16_to_cpu(event->code);
+    evdev.value = le32_to_cpu(event->value);
+
+    rc = write(vih->fd, &evdev, sizeof(evdev));
+    if (rc == -1) {
+        perror("virtio_input_host_handle_status: write");
+    }
+}
+
 static const VMStateDescription vmstate_virtio_input_host = {
     .name = "virtio-input-host",
     .unmigratable = 1,
@@ -164,6 +229,7 @@ static void virtio_input_host_class_init(ObjectClass *klass, void *data)
     dc->props          = virtio_input_host_properties;
     vic->realize       = virtio_input_host_realize;
     vic->unrealize     = virtio_input_host_unrealize;
+    vic->handle_status = virtio_input_host_handle_status;
 }
 
 static void virtio_input_host_init(Object *obj)
diff --git a/hw/input/virtio-input.c b/hw/input/virtio-input.c
index 672c207eb5..f59749a943 100644
--- a/hw/input/virtio-input.c
+++ b/hw/input/virtio-input.c
@@ -14,6 +14,8 @@
 
 #include "standard-headers/linux/input.h"
 
+#define VIRTIO_INPUT_VM_VERSION 1
+
 /* ----------------------------------------------------------------- */
 
 void virtio_input_send(VirtIOInput *vinput, virtio_input_event *event)
@@ -97,9 +99,9 @@ static void virtio_input_handle_sts(VirtIODevice *vdev, VirtQueue *vq)
     virtio_notify(vdev, vinput->sts);
 }
 
-static virtio_input_config *virtio_input_find_config(VirtIOInput *vinput,
-                                                     uint8_t select,
-                                                     uint8_t subsel)
+virtio_input_config *virtio_input_find_config(VirtIOInput *vinput,
+                                              uint8_t select,
+                                              uint8_t subsel)
 {
     VirtIOInputConfig *cfg;
 
@@ -214,6 +216,38 @@ static void virtio_input_reset(VirtIODevice *vdev)
     }
 }
 
+static void virtio_input_save(QEMUFile *f, void *opaque)
+{
+    VirtIOInput *vinput = opaque;
+    VirtIODevice *vdev = VIRTIO_DEVICE(vinput);
+
+    virtio_save(vdev, f);
+}
+
+static int virtio_input_load(QEMUFile *f, void *opaque, int version_id)
+{
+    VirtIOInput *vinput = opaque;
+    VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(vinput);
+    VirtIODevice *vdev = VIRTIO_DEVICE(vinput);
+    int ret;
+
+    if (version_id != VIRTIO_INPUT_VM_VERSION) {
+        return -EINVAL;
+    }
+
+    ret = virtio_load(vdev, f, version_id);
+    if (ret) {
+        return ret;
+    }
+
+    /* post_load() */
+    vinput->active = vdev->status & VIRTIO_CONFIG_S_DRIVER_OK;
+    if (vic->change_active) {
+        vic->change_active(vinput);
+    }
+    return 0;
+}
+
 static void virtio_input_device_realize(DeviceState *dev, Error **errp)
 {
     VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(dev);
@@ -245,14 +279,20 @@ static void virtio_input_device_realize(DeviceState *dev, Error **errp)
                 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);
+
+    register_savevm(dev, "virtio-input", -1, VIRTIO_INPUT_VM_VERSION,
+                    virtio_input_save, virtio_input_load, vinput);
 }
 
 static void virtio_input_device_unrealize(DeviceState *dev, Error **errp)
 {
     VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(dev);
     VirtIODevice *vdev = VIRTIO_DEVICE(dev);
+    VirtIOInput *vinput = VIRTIO_INPUT(dev);
     Error *local_err = NULL;
 
+    unregister_savevm(dev, "virtio-input", vinput);
+
     if (vic->unrealize) {
         vic->unrealize(dev, &local_err);
         if (local_err) {
diff --git a/include/hw/virtio/virtio-input.h b/include/hw/virtio/virtio-input.h
index af1c207ab1..bddbd4b287 100644
--- a/include/hw/virtio/virtio-input.h
+++ b/include/hw/virtio/virtio-input.h
@@ -13,20 +13,6 @@ typedef struct virtio_input_absinfo virtio_input_absinfo;
 typedef struct virtio_input_config virtio_input_config;
 typedef struct virtio_input_event virtio_input_event;
 
-#if defined(HOST_WORDS_BIGENDIAN)
-# define const_le32(_x)                          \
-    (((_x & 0x000000ffU) << 24) |                \
-     ((_x & 0x0000ff00U) <<  8) |                \
-     ((_x & 0x00ff0000U) >>  8) |                \
-     ((_x & 0xff000000U) >> 24))
-# define const_le16(_x)                          \
-    (((_x & 0x00ff) << 8) |                      \
-     ((_x & 0xff00) >> 8))
-#else
-# define const_le32(_x) (_x)
-# define const_le16(_x) (_x)
-#endif
-
 /* ----------------------------------------------------------------- */
 /* qemu internals                                                    */
 
@@ -111,6 +97,9 @@ struct VirtIOInputHost {
 void virtio_input_send(VirtIOInput *vinput, virtio_input_event *event);
 void virtio_input_init_config(VirtIOInput *vinput,
                               virtio_input_config *config);
+virtio_input_config *virtio_input_find_config(VirtIOInput *vinput,
+                                              uint8_t select,
+                                              uint8_t subsel);
 void virtio_input_add_config(VirtIOInput *vinput,
                              virtio_input_config *config);
 void virtio_input_idstr_config(VirtIOInput *vinput,
diff --git a/include/qemu/bswap.h b/include/qemu/bswap.h
index fcedf0d249..ce3c42e4d9 100644
--- a/include/qemu/bswap.h
+++ b/include/qemu/bswap.h
@@ -125,6 +125,25 @@ static inline uint32_t qemu_bswap_len(uint32_t value, int len)
     return bswap32(value) >> (32 - 8 * len);
 }
 
+/*
+ * Same as cpu_to_le{16,23}, except that gcc will figure the result is
+ * a compile-time constant if you pass in a constant.  So this can be
+ * used to initialize static variables.
+ */
+#if defined(HOST_WORDS_BIGENDIAN)
+# define const_le32(_x)                          \
+    ((((_x) & 0x000000ffU) << 24) |              \
+     (((_x) & 0x0000ff00U) <<  8) |              \
+     (((_x) & 0x00ff0000U) >>  8) |              \
+     (((_x) & 0xff000000U) >> 24))
+# define const_le16(_x)                          \
+    ((((_x) & 0x00ff) << 8) |                    \
+     (((_x) & 0xff00) >> 8))
+#else
+# define const_le32(_x) (_x)
+# define const_le16(_x) (_x)
+#endif
+
 /* Unions for reinterpreting between floats and integers.  */
 
 typedef union {
diff --git a/ui/input-linux.c b/ui/input-linux.c
index 9c921cc0ad..1d33b5c121 100644
--- a/ui/input-linux.c
+++ b/ui/input-linux.c
@@ -337,7 +337,7 @@ static void input_linux_event_mouse(void *opaque)
 static void input_linux_complete(UserCreatable *uc, Error **errp)
 {
     InputLinux *il = INPUT_LINUX(uc);
-    uint32_t evtmap;
+    uint32_t evtmap, relmap, absmap;
     int rc, ver;
 
     if (!il->evdev) {
@@ -359,16 +359,36 @@ static void input_linux_complete(UserCreatable *uc, Error **errp)
     }
 
     rc = ioctl(il->fd, EVIOCGBIT(0, sizeof(evtmap)), &evtmap);
+    if (rc < 0) {
+        error_setg(errp, "%s: failed to read event bits", il->evdev);
+        goto err_close;
+    }
 
     if (evtmap & (1 << EV_REL)) {
-        /* has relative axis -> assume mouse */
+        rc = ioctl(il->fd, EVIOCGBIT(EV_REL, sizeof(relmap)), &relmap);
+        if (rc < 0) {
+            relmap = 0;
+        }
+    }
+
+    if (evtmap & (1 << EV_ABS)) {
+        ioctl(il->fd, EVIOCGBIT(EV_ABS, sizeof(absmap)), &absmap);
+        if (rc < 0) {
+            absmap = 0;
+        }
+    }
+
+    if ((evtmap & (1 << EV_REL)) &&
+        (relmap & (1 << REL_X))) {
+        /* has relative x axis -> assume mouse */
         qemu_set_fd_handler(il->fd, input_linux_event_mouse, NULL, il);
-    } else if (evtmap & (1 << EV_ABS)) {
-        /* has absolute axis -> not supported */
+    } else if ((evtmap & (1 << EV_ABS)) &&
+               (absmap & (1 << ABS_X))) {
+        /* has absolute x axis -> not supported */
         error_setg(errp, "tablet/touchscreen not supported");
         goto err_close;
     } else if (evtmap & (1 << EV_KEY)) {
-        /* has keys/buttons (and no axis) -> assume keyboard */
+        /* has keys/buttons (and no x axis) -> assume keyboard */
         qemu_set_fd_handler(il->fd, input_linux_event_keyboard, NULL, il);
     } else {
         /* Huh? What is this? */