macvtap/tun: cross-endian support for little-endian hosts
authorGreg Kurz <gkurz@linux.vnet.ibm.com>
Fri, 24 Apr 2015 12:50:36 +0000 (14:50 +0200)
committerMichael S. Tsirkin <mst@redhat.com>
Mon, 1 Jun 2015 13:48:56 +0000 (15:48 +0200)
The VNET_LE flag was introduced to fix accesses to virtio 1.0 headers
that are always little-endian. It can also be used to handle the special
case of a legacy little-endian device implemented by a big-endian host.

Let's add a flag and ioctls for big-endian devices as well. If both flags
are set, little-endian wins.

Since this is isn't a common usecase, the feature is controlled by a kernel
config option (not set by default).

Both macvtap and tun are covered by this patch since they share the same
API with userland.

Signed-off-by: Greg Kurz <gkurz@linux.vnet.ibm.com>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Reviewed-by: David Gibson <david@gibson.dropbear.id.au>
drivers/net/Kconfig
drivers/net/macvtap.c
drivers/net/tun.c
include/uapi/linux/if_tun.h

index df51d60..71ac0ec 100644 (file)
@@ -244,6 +244,20 @@ config TUN
 
          If you don't know what to use this for, you don't need it.
 
+config TUN_VNET_CROSS_LE
+       bool "Support for cross-endian vnet headers on little-endian kernels"
+       default n
+       ---help---
+         This option allows TUN/TAP and MACVTAP device drivers in a
+         little-endian kernel to parse vnet headers that come from a
+         big-endian legacy virtio device.
+
+         Userspace programs can control the feature using the TUNSETVNETBE
+         and TUNGETVNETBE ioctls.
+
+         Unless you have a little-endian system hosting a big-endian virtual
+         machine with a legacy virtio NIC, you should say N.
+
 config VETH
        tristate "Virtual ethernet pair device"
        ---help---
index 072ccba..928f3f4 100644 (file)
@@ -48,11 +48,60 @@ struct macvtap_queue {
 #define MACVTAP_FEATURES (IFF_VNET_HDR | IFF_MULTI_QUEUE)
 
 #define MACVTAP_VNET_LE 0x80000000
+#define MACVTAP_VNET_BE 0x40000000
+
+#ifdef CONFIG_TUN_VNET_CROSS_LE
+static inline bool macvtap_legacy_is_little_endian(struct macvtap_queue *q)
+{
+       return q->flags & MACVTAP_VNET_BE ? false :
+               virtio_legacy_is_little_endian();
+}
+
+static long macvtap_get_vnet_be(struct macvtap_queue *q, int __user *sp)
+{
+       int s = !!(q->flags & MACVTAP_VNET_BE);
+
+       if (put_user(s, sp))
+               return -EFAULT;
+
+       return 0;
+}
+
+static long macvtap_set_vnet_be(struct macvtap_queue *q, int __user *sp)
+{
+       int s;
+
+       if (get_user(s, sp))
+               return -EFAULT;
+
+       if (s)
+               q->flags |= MACVTAP_VNET_BE;
+       else
+               q->flags &= ~MACVTAP_VNET_BE;
+
+       return 0;
+}
+#else
+static inline bool macvtap_legacy_is_little_endian(struct macvtap_queue *q)
+{
+       return virtio_legacy_is_little_endian();
+}
+
+static long macvtap_get_vnet_be(struct macvtap_queue *q, int __user *argp)
+{
+       return -EINVAL;
+}
+
+static long macvtap_set_vnet_be(struct macvtap_queue *q, int __user *argp)
+{
+       return -EINVAL;
+}
+#endif /* CONFIG_TUN_VNET_CROSS_LE */
 
 static inline bool macvtap_is_little_endian(struct macvtap_queue *q)
 {
        return q->flags & MACVTAP_VNET_LE ||
-               virtio_legacy_is_little_endian();
+               macvtap_legacy_is_little_endian(q);
 }
 
 static inline u16 macvtap16_to_cpu(struct macvtap_queue *q, __virtio16 val)
@@ -1096,6 +1145,12 @@ static long macvtap_ioctl(struct file *file, unsigned int cmd,
                        q->flags &= ~MACVTAP_VNET_LE;
                return 0;
 
+       case TUNGETVNETBE:
+               return macvtap_get_vnet_be(q, sp);
+
+       case TUNSETVNETBE:
+               return macvtap_set_vnet_be(q, sp);
+
        case TUNSETOFFLOAD:
                /* let the user check for future flags */
                if (arg & ~(TUN_F_CSUM | TUN_F_TSO4 | TUN_F_TSO6 |
index b210139..cb376b2 100644 (file)
@@ -111,6 +111,7 @@ do {                                                                \
 #define TUN_FASYNC     IFF_ATTACH_QUEUE
 /* High bits in flags field are unused. */
 #define TUN_VNET_LE     0x80000000
+#define TUN_VNET_BE     0x40000000
 
 #define TUN_FEATURES (IFF_NO_PI | IFF_ONE_QUEUE | IFF_VNET_HDR | \
                      IFF_MULTI_QUEUE)
@@ -206,10 +207,58 @@ struct tun_struct {
        u32 flow_count;
 };
 
+#ifdef CONFIG_TUN_VNET_CROSS_LE
+static inline bool tun_legacy_is_little_endian(struct tun_struct *tun)
+{
+       return tun->flags & TUN_VNET_BE ? false :
+               virtio_legacy_is_little_endian();
+}
+
+static long tun_get_vnet_be(struct tun_struct *tun, int __user *argp)
+{
+       int be = !!(tun->flags & TUN_VNET_BE);
+
+       if (put_user(be, argp))
+               return -EFAULT;
+
+       return 0;
+}
+
+static long tun_set_vnet_be(struct tun_struct *tun, int __user *argp)
+{
+       int be;
+
+       if (get_user(be, argp))
+               return -EFAULT;
+
+       if (be)
+               tun->flags |= TUN_VNET_BE;
+       else
+               tun->flags &= ~TUN_VNET_BE;
+
+       return 0;
+}
+#else
+static inline bool tun_legacy_is_little_endian(struct tun_struct *tun)
+{
+       return virtio_legacy_is_little_endian();
+}
+
+static long tun_get_vnet_be(struct tun_struct *tun, int __user *argp)
+{
+       return -EINVAL;
+}
+
+static long tun_set_vnet_be(struct tun_struct *tun, int __user *argp)
+{
+       return -EINVAL;
+}
+#endif /* CONFIG_TUN_VNET_CROSS_LE */
+
 static inline bool tun_is_little_endian(struct tun_struct *tun)
 {
        return tun->flags & TUN_VNET_LE ||
-               virtio_legacy_is_little_endian();
+               tun_legacy_is_little_endian(tun);
 }
 
 static inline u16 tun16_to_cpu(struct tun_struct *tun, __virtio16 val)
@@ -2062,6 +2111,14 @@ static long __tun_chr_ioctl(struct file *file, unsigned int cmd,
                        tun->flags &= ~TUN_VNET_LE;
                break;
 
+       case TUNGETVNETBE:
+               ret = tun_get_vnet_be(tun, argp);
+               break;
+
+       case TUNSETVNETBE:
+               ret = tun_set_vnet_be(tun, argp);
+               break;
+
        case TUNATTACHFILTER:
                /* Can be set only for TAPs */
                ret = -EINVAL;
index 50ae243..3cb5e1d 100644 (file)
 #define TUNGETFILTER _IOR('T', 219, struct sock_fprog)
 #define TUNSETVNETLE _IOW('T', 220, int)
 #define TUNGETVNETLE _IOR('T', 221, int)
+/* The TUNSETVNETBE and TUNGETVNETBE ioctls are for cross-endian support on
+ * little-endian hosts. Not all kernel configurations support them, but all
+ * configurations that support SET also support GET.
+ */
+#define TUNSETVNETBE _IOW('T', 222, int)
+#define TUNGETVNETBE _IOR('T', 223, int)
 
 /* TUNSETIFF ifr flags */
 #define IFF_TUN                0x0001