vhost: cross-endian support for legacy devices
authorGreg Kurz <gkurz@linux.vnet.ibm.com>
Fri, 24 Apr 2015 12:27:24 +0000 (14:27 +0200)
committerMichael S. Tsirkin <mst@redhat.com>
Mon, 1 Jun 2015 13:48:55 +0000 (15:48 +0200)
This patch brings cross-endian support to vhost when used to implement
legacy virtio devices. Since it is a relatively rare situation, the
feature availability is controlled by a kernel config option (not set
by default).

The vq->is_le boolean field is added to cache the endianness to be
used for ring accesses. It defaults to native endian, as expected
by legacy virtio devices. When the ring gets active, we force little
endian if the device is modern. When the ring is deactivated, we
revert to the native endian default.

If cross-endian was compiled in, a vq->user_be boolean field is added
so that userspace may request a specific endianness. This field is
used to override the default when activating the ring of a legacy
device. It has no effect on modern devices.

Signed-off-by: Greg Kurz <gkurz@linux.vnet.ibm.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Reviewed-by: Cornelia Huck <cornelia.huck@de.ibm.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
drivers/vhost/Kconfig
drivers/vhost/vhost.c
drivers/vhost/vhost.h
include/uapi/linux/vhost.h

index 017a1e8a8f6fcf4eb092fa48584aef00dddf5f17..533eaf04f12faff85ba90791647c08a1f7a17599 100644 (file)
@@ -32,3 +32,18 @@ config VHOST
        ---help---
          This option is selected by any driver which needs to access
          the core of vhost.
+
+config VHOST_CROSS_ENDIAN_LEGACY
+       bool "Cross-endian support for vhost"
+       default n
+       ---help---
+         This option allows vhost to support guests with a different byte
+         ordering from host while using legacy virtio.
+
+         Userspace programs can control the feature using the
+         VHOST_SET_VRING_ENDIAN and VHOST_GET_VRING_ENDIAN ioctls.
+
+         This is only useful on a few platforms (ppc64 and arm64). Since it
+         adds some overhead, it is disabled by default.
+
+         If unsure, say "N".
index 2ee28266fd0704fd1e1c4c64a6f19c8d863727fd..9e8e004bb1c38d809c2af43b9d42c053db3a41a2 100644 (file)
@@ -36,6 +36,77 @@ enum {
 #define vhost_used_event(vq) ((__virtio16 __user *)&vq->avail->ring[vq->num])
 #define vhost_avail_event(vq) ((__virtio16 __user *)&vq->used->ring[vq->num])
 
+#ifdef CONFIG_VHOST_CROSS_ENDIAN_LEGACY
+static void vhost_vq_reset_user_be(struct vhost_virtqueue *vq)
+{
+       vq->user_be = !virtio_legacy_is_little_endian();
+}
+
+static long vhost_set_vring_endian(struct vhost_virtqueue *vq, int __user *argp)
+{
+       struct vhost_vring_state s;
+
+       if (vq->private_data)
+               return -EBUSY;
+
+       if (copy_from_user(&s, argp, sizeof(s)))
+               return -EFAULT;
+
+       if (s.num != VHOST_VRING_LITTLE_ENDIAN &&
+           s.num != VHOST_VRING_BIG_ENDIAN)
+               return -EINVAL;
+
+       vq->user_be = s.num;
+
+       return 0;
+}
+
+static long vhost_get_vring_endian(struct vhost_virtqueue *vq, u32 idx,
+                                  int __user *argp)
+{
+       struct vhost_vring_state s = {
+               .index = idx,
+               .num = vq->user_be
+       };
+
+       if (copy_to_user(argp, &s, sizeof(s)))
+               return -EFAULT;
+
+       return 0;
+}
+
+static void vhost_init_is_le(struct vhost_virtqueue *vq)
+{
+       /* Note for legacy virtio: user_be is initialized at reset time
+        * according to the host endianness. If userspace does not set an
+        * explicit endianness, the default behavior is native endian, as
+        * expected by legacy virtio.
+        */
+       vq->is_le = vhost_has_feature(vq, VIRTIO_F_VERSION_1) || !vq->user_be;
+}
+#else
+static void vhost_vq_reset_user_be(struct vhost_virtqueue *vq)
+{
+}
+
+static long vhost_set_vring_endian(struct vhost_virtqueue *vq, int __user *argp)
+{
+       return -ENOIOCTLCMD;
+}
+
+static long vhost_get_vring_endian(struct vhost_virtqueue *vq, u32 idx,
+                                  int __user *argp)
+{
+       return -ENOIOCTLCMD;
+}
+
+static void vhost_init_is_le(struct vhost_virtqueue *vq)
+{
+       if (vhost_has_feature(vq, VIRTIO_F_VERSION_1))
+               vq->is_le = true;
+}
+#endif /* CONFIG_VHOST_CROSS_ENDIAN_LEGACY */
+
 static void vhost_poll_func(struct file *file, wait_queue_head_t *wqh,
                            poll_table *pt)
 {
@@ -199,6 +270,8 @@ static void vhost_vq_reset(struct vhost_dev *dev,
        vq->call = NULL;
        vq->log_ctx = NULL;
        vq->memory = NULL;
+       vq->is_le = virtio_legacy_is_little_endian();
+       vhost_vq_reset_user_be(vq);
 }
 
 static int vhost_worker(void *data)
@@ -806,6 +879,12 @@ long vhost_vring_ioctl(struct vhost_dev *d, int ioctl, void __user *argp)
                } else
                        filep = eventfp;
                break;
+       case VHOST_SET_VRING_ENDIAN:
+               r = vhost_set_vring_endian(vq, argp);
+               break;
+       case VHOST_GET_VRING_ENDIAN:
+               r = vhost_get_vring_endian(vq, idx, argp);
+               break;
        default:
                r = -ENOIOCTLCMD;
        }
@@ -1044,8 +1123,12 @@ int vhost_init_used(struct vhost_virtqueue *vq)
 {
        __virtio16 last_used_idx;
        int r;
-       if (!vq->private_data)
+       if (!vq->private_data) {
+               vq->is_le = virtio_legacy_is_little_endian();
                return 0;
+       }
+
+       vhost_init_is_le(vq);
 
        r = vhost_update_used_flags(vq);
        if (r)
index a4fa33a79bf2c04b258f16e705ddc70750bec4d2..ce6f6da4b09f988bc4ae15268912335fcd1bab18 100644 (file)
@@ -106,6 +106,14 @@ struct vhost_virtqueue {
        /* Log write descriptors */
        void __user *log_base;
        struct vhost_log *log;
+
+       /* Ring endianness. Defaults to legacy native endianness.
+        * Set to true when starting a modern virtio device. */
+       bool is_le;
+#ifdef CONFIG_VHOST_CROSS_ENDIAN_LEGACY
+       /* Ring endianness requested by userspace for cross-endian support. */
+       bool user_be;
+#endif
 };
 
 struct vhost_dev {
@@ -175,8 +183,7 @@ static inline bool vhost_has_feature(struct vhost_virtqueue *vq, int bit)
 
 static inline bool vhost_is_little_endian(struct vhost_virtqueue *vq)
 {
-       return vhost_has_feature(vq, VIRTIO_F_VERSION_1) ||
-               virtio_legacy_is_little_endian();
+       return vq->is_le;
 }
 
 /* Memory accessors */
index bb6a5b4cb3c558eb7d82fce2ab2a9b8b0fadf0ca..ab3731917bac326a3a94ac1baa174bdcb35ceaab 100644 (file)
@@ -103,6 +103,20 @@ struct vhost_memory {
 /* Get accessor: reads index, writes value in num */
 #define VHOST_GET_VRING_BASE _IOWR(VHOST_VIRTIO, 0x12, struct vhost_vring_state)
 
+/* Set the vring byte order in num. Valid values are VHOST_VRING_LITTLE_ENDIAN
+ * or VHOST_VRING_BIG_ENDIAN (other values return -EINVAL).
+ * The byte order cannot be changed while the device is active: trying to do so
+ * returns -EBUSY.
+ * This is a legacy only API that is simply ignored when VIRTIO_F_VERSION_1 is
+ * set.
+ * Not all kernel configurations support this ioctl, but all configurations that
+ * support SET also support GET.
+ */
+#define VHOST_VRING_LITTLE_ENDIAN 0
+#define VHOST_VRING_BIG_ENDIAN 1
+#define VHOST_SET_VRING_ENDIAN _IOW(VHOST_VIRTIO, 0x13, struct vhost_vring_state)
+#define VHOST_GET_VRING_ENDIAN _IOW(VHOST_VIRTIO, 0x14, struct vhost_vring_state)
+
 /* The following ioctls use eventfd file descriptors to signal and poll
  * for events. */