Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8b8e658b authored by Greg Kurz's avatar Greg Kurz Committed by Michael S. Tsirkin
Browse files

macvtap/tun: cross-endian support for little-endian hosts



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: default avatarGreg Kurz <gkurz@linux.vnet.ibm.com>

Signed-off-by: default avatarMichael S. Tsirkin <mst@redhat.com>
Reviewed-by: default avatarDavid Gibson <david@gibson.dropbear.id.au>
parent 2751c988
Loading
Loading
Loading
Loading
+14 −0
Original line number Diff line number Diff line
@@ -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---
+56 −1
Original line number Diff line number Diff line
@@ -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 |
+58 −1
Original line number Diff line number Diff line
@@ -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;
+6 −0
Original line number Diff line number Diff line
@@ -50,6 +50,12 @@
#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