Skip to content

Commit

Permalink
Merge tag 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/gi…
Browse files Browse the repository at this point in the history
…t/mst/vhost

Pull virtio/vhost cross endian support from Michael Tsirkin:
 "I have just queued some more bugfix patches today but none fix
  regressions and none are related to these ones, so it looks like a
  good time for a merge for -rc1.

  The motivation for this is support for legacy BE guests on the new LE
  hosts.  There are two redeeming properties that made me merge this:

   - It's a trivial amount of code: since we wrap host/guest accesses
     anyway, almost all of it is well hidden from drivers.

   - Sane platforms would never set flags like VHOST_CROSS_ENDIAN_LEGACY,
     and when it's clear, there's zero overhead (as some point it was
     tested by compiling with and without the patches, got the same
     stripped binary).

  Maybe we could create a Kconfig symbol to enforce the second point:
  prevent people from enabling it eg on x86.  I will look into this"

* tag 'for_linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mst/vhost:
  virtio-pci: alloc only resources actually used.
  macvtap/tun: cross-endian support for little-endian hosts
  vhost: cross-endian support for legacy devices
  virtio: add explicit big-endian support to memory accessors
  vhost: introduce vhost_is_little_endian() helper
  vringh: introduce vringh_is_little_endian() helper
  macvtap: introduce macvtap_is_little_endian() helper
  tun: add tun_is_little_endian() helper
  virtio: introduce virtio_is_little_endian() helper
  • Loading branch information
Linus Torvalds committed Jul 3, 2015
2 parents 0cbee99 + 59a5b0f commit 5fc8352
Show file tree
Hide file tree
Showing 15 changed files with 350 additions and 47 deletions.
14 changes: 14 additions & 0 deletions drivers/net/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,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---
Expand Down
65 changes: 63 additions & 2 deletions drivers/net/macvtap.c
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,70 @@ 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 ||
macvtap_legacy_is_little_endian(q);
}

static inline u16 macvtap16_to_cpu(struct macvtap_queue *q, __virtio16 val)
{
return __virtio16_to_cpu(q->flags & MACVTAP_VNET_LE, val);
return __virtio16_to_cpu(macvtap_is_little_endian(q), val);
}

static inline __virtio16 cpu_to_macvtap16(struct macvtap_queue *q, u16 val)
{
return __cpu_to_virtio16(q->flags & MACVTAP_VNET_LE, val);
return __cpu_to_virtio16(macvtap_is_little_endian(q), val);
}

static struct proto macvtap_proto = {
Expand Down Expand Up @@ -1085,6 +1140,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 |
Expand Down
67 changes: 65 additions & 2 deletions drivers/net/tun.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -205,14 +206,68 @@ 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 ||
tun_legacy_is_little_endian(tun);
}

static inline u16 tun16_to_cpu(struct tun_struct *tun, __virtio16 val)
{
return __virtio16_to_cpu(tun->flags & TUN_VNET_LE, val);
return __virtio16_to_cpu(tun_is_little_endian(tun), val);
}

static inline __virtio16 cpu_to_tun16(struct tun_struct *tun, u16 val)
{
return __cpu_to_virtio16(tun->flags & TUN_VNET_LE, val);
return __cpu_to_virtio16(tun_is_little_endian(tun), val);
}

static inline u32 tun_hashfn(u32 rxhash)
Expand Down Expand Up @@ -2044,6 +2099,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;
Expand Down
15 changes: 15 additions & 0 deletions drivers/vhost/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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".
85 changes: 84 additions & 1 deletion drivers/vhost/vhost.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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)
Expand Down
25 changes: 19 additions & 6 deletions drivers/vhost/vhost.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -173,34 +181,39 @@ static inline bool vhost_has_feature(struct vhost_virtqueue *vq, int bit)
return vq->acked_features & (1ULL << bit);
}

static inline bool vhost_is_little_endian(struct vhost_virtqueue *vq)
{
return vq->is_le;
}

/* Memory accessors */
static inline u16 vhost16_to_cpu(struct vhost_virtqueue *vq, __virtio16 val)
{
return __virtio16_to_cpu(vhost_has_feature(vq, VIRTIO_F_VERSION_1), val);
return __virtio16_to_cpu(vhost_is_little_endian(vq), val);
}

static inline __virtio16 cpu_to_vhost16(struct vhost_virtqueue *vq, u16 val)
{
return __cpu_to_virtio16(vhost_has_feature(vq, VIRTIO_F_VERSION_1), val);
return __cpu_to_virtio16(vhost_is_little_endian(vq), val);
}

static inline u32 vhost32_to_cpu(struct vhost_virtqueue *vq, __virtio32 val)
{
return __virtio32_to_cpu(vhost_has_feature(vq, VIRTIO_F_VERSION_1), val);
return __virtio32_to_cpu(vhost_is_little_endian(vq), val);
}

static inline __virtio32 cpu_to_vhost32(struct vhost_virtqueue *vq, u32 val)
{
return __cpu_to_virtio32(vhost_has_feature(vq, VIRTIO_F_VERSION_1), val);
return __cpu_to_virtio32(vhost_is_little_endian(vq), val);
}

static inline u64 vhost64_to_cpu(struct vhost_virtqueue *vq, __virtio64 val)
{
return __virtio64_to_cpu(vhost_has_feature(vq, VIRTIO_F_VERSION_1), val);
return __virtio64_to_cpu(vhost_is_little_endian(vq), val);
}

static inline __virtio64 cpu_to_vhost64(struct vhost_virtqueue *vq, u64 val)
{
return __cpu_to_virtio64(vhost_has_feature(vq, VIRTIO_F_VERSION_1), val);
return __cpu_to_virtio64(vhost_is_little_endian(vq), val);
}
#endif
Loading

0 comments on commit 5fc8352

Please sign in to comment.