Details about this can be found here: https://lwn.net/Articles/750770/ What's new in this version ========================== There are three choices based on the discussion [1] in RFC v2: > #1. We expose a VFIO device, so we can reuse the VFIO container/group > based DMA API and potentially reuse a lot of VFIO code in QEMU. > > But in this case, we have two choices for the VFIO device interface > (i.e. the interface on top of VFIO device fd): > > A) we may invent a new vhost protocol (as demonstrated by the code > in this RFC) on VFIO device fd to make it work in VFIO's way, > i.e. regions and irqs. > > B) Or as you proposed, instead of inventing a new vhost protocol, > we can reuse most existing vhost ioctls on the VFIO device fd > directly. There should be no conflicts between the VFIO ioctls > (type is 0x3B) and VHOST ioctls (type is 0xAF) currently. > > #2. Instead of exposing a VFIO device, we may expose a VHOST device. > And we will introduce a new mdev driver vhost-mdev to do this. > It would be natural to reuse the existing kernel vhost interface > (ioctls) on it as much as possible. But we will need to invent > some APIs for DMA programming (reusing VHOST_SET_MEM_TABLE is a > choice, but it's too heavy and doesn't support vIOMMU by itself). This version is more like a quick PoC to try Jason's proposal on reusing vhost ioctls. And the second way (#1/B) in above three choices was chosen in this version to demonstrate the idea quickly. Now the userspace API looks like this: - VFIO's container/group based IOMMU API is used to do the DMA programming. - Vhost's existing ioctls are used to setup the device. And the device will report device_api as "vfio-vhost". Note that, there are dirty hacks in this version. If we decide to go this way, some refactoring in vhost.c/vhost.h may be needed. PS. The direct mapping of the notify registers isn't implemented in this version. [1] https://lkml.org/lkml/2019/7/9/101 Signed-off-by: Tiwei Bie <tiwei.bie@xxxxxxxxx> --- drivers/vhost/Kconfig | 9 + drivers/vhost/Makefile | 3 + drivers/vhost/mdev.c | 382 +++++++++++++++++++++++++++++++++++++ include/linux/vhost_mdev.h | 58 ++++++ include/uapi/linux/vfio.h | 2 + include/uapi/linux/vhost.h | 8 + 6 files changed, 462 insertions(+) create mode 100644 drivers/vhost/mdev.c create mode 100644 include/linux/vhost_mdev.h diff --git a/drivers/vhost/Kconfig b/drivers/vhost/Kconfig index 3d03ccbd1adc..2ba54fcf43b7 100644 --- a/drivers/vhost/Kconfig +++ b/drivers/vhost/Kconfig @@ -34,6 +34,15 @@ config VHOST_VSOCK To compile this driver as a module, choose M here: the module will be called vhost_vsock. +config VHOST_MDEV + tristate "Hardware vhost accelerator abstraction" + depends on EVENTFD && VFIO && VFIO_MDEV + select VHOST + default n + ---help--- + Say Y here to enable the vhost_mdev module + for use with hardware vhost accelerators + config VHOST tristate ---help--- diff --git a/drivers/vhost/Makefile b/drivers/vhost/Makefile index 6c6df24f770c..ad9c0f8c6d8c 100644 --- a/drivers/vhost/Makefile +++ b/drivers/vhost/Makefile @@ -10,4 +10,7 @@ vhost_vsock-y := vsock.o obj-$(CONFIG_VHOST_RING) += vringh.o +obj-$(CONFIG_VHOST_MDEV) += vhost_mdev.o +vhost_mdev-y := mdev.o + obj-$(CONFIG_VHOST) += vhost.o diff --git a/drivers/vhost/mdev.c b/drivers/vhost/mdev.c new file mode 100644 index 000000000000..6bef1d9ae2e6 --- /dev/null +++ b/drivers/vhost/mdev.c @@ -0,0 +1,382 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018-2019 Intel Corporation. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/vfio.h> +#include <linux/vhost.h> +#include <linux/mdev.h> +#include <linux/vhost_mdev.h> + +#include "vhost.h" + +struct vhost_mdev { + struct vhost_dev dev; + bool opened; + int nvqs; + u64 state; + u64 acked_features; + u64 features; + const struct vhost_mdev_device_ops *ops; + struct mdev_device *mdev; + void *private; + struct vhost_virtqueue vqs[]; +}; + +static void handle_vq_kick(struct vhost_work *work) +{ + struct vhost_virtqueue *vq = container_of(work, struct vhost_virtqueue, + poll.work); + struct vhost_mdev *vdpa = container_of(vq->dev, struct vhost_mdev, dev); + + vdpa->ops->notify(vdpa, vq - vdpa->vqs); +} + +static int vhost_set_state(struct vhost_mdev *vdpa, u64 __user *statep) +{ + u64 state; + + if (copy_from_user(&state, statep, sizeof(state))) + return -EFAULT; + + if (state >= VHOST_MDEV_S_MAX) + return -EINVAL; + + if (vdpa->state == state) + return 0; + + mutex_lock(&vdpa->dev.mutex); + + vdpa->state = state; + + switch (vdpa->state) { + case VHOST_MDEV_S_RUNNING: + vdpa->ops->start(vdpa); + break; + case VHOST_MDEV_S_STOPPED: + vdpa->ops->stop(vdpa); + break; + } + + mutex_unlock(&vdpa->dev.mutex); + + return 0; +} + +static int vhost_set_features(struct vhost_mdev *vdpa, u64 __user *featurep) +{ + u64 features; + + if (copy_from_user(&features, featurep, sizeof(features))) + return -EFAULT; + + if (features & ~vdpa->features) + return -EINVAL; + + vdpa->acked_features = features; + vdpa->ops->features_changed(vdpa); + return 0; +} + +static int vhost_get_features(struct vhost_mdev *vdpa, u64 __user *featurep) +{ + if (copy_to_user(featurep, &vdpa->features, sizeof(vdpa->features))) + return -EFAULT; + return 0; +} + +static int vhost_get_vring_base(struct vhost_mdev *vdpa, void __user *argp) +{ + struct vhost_virtqueue *vq; + u32 idx; + int r; + + r = get_user(idx, (u32 __user *)argp); + if (r < 0) + return r; + + vq = &vdpa->vqs[idx]; + vq->last_avail_idx = vdpa->ops->get_vring_base(vdpa, idx); + + return vhost_vring_ioctl(&vdpa->dev, VHOST_GET_VRING_BASE, argp); +} + +/* + * Helpers for backend to register mdev. + */ + +struct vhost_mdev *vhost_mdev_alloc(struct mdev_device *mdev, void *private, + int nvqs) +{ + struct vhost_mdev *vdpa; + struct vhost_dev *dev; + struct vhost_virtqueue **vqs; + size_t size; + int i; + + size = sizeof(struct vhost_mdev) + nvqs * sizeof(struct vhost_virtqueue); + + vdpa = kzalloc(size, GFP_KERNEL); + if (!vdpa) + return NULL; + + vdpa->nvqs = nvqs; + + vqs = kmalloc_array(nvqs, sizeof(*vqs), GFP_KERNEL); + if (!vqs) { + kfree(vdpa); + return NULL; + } + + dev = &vdpa->dev; + for (i = 0; i < nvqs; i++) { + vqs[i] = &vdpa->vqs[i]; + vqs[i]->handle_kick = handle_vq_kick; + } + vhost_dev_init(dev, vqs, nvqs, 0, 0, 0); + + vdpa->private = private; + vdpa->mdev = mdev; + + mdev_set_drvdata(mdev, vdpa); + + return vdpa; +} +EXPORT_SYMBOL(vhost_mdev_alloc); + +void vhost_mdev_free(struct vhost_mdev *vdpa) +{ + struct mdev_device *mdev; + + mdev = vdpa->mdev; + mdev_set_drvdata(mdev, NULL); + + vhost_dev_stop(&vdpa->dev); + vhost_dev_cleanup(&vdpa->dev); + kfree(vdpa->dev.vqs); + kfree(vdpa); +} +EXPORT_SYMBOL(vhost_mdev_free); + +ssize_t vhost_mdev_read(struct mdev_device *mdev, char __user *buf, + size_t count, loff_t *ppos) +{ + return -EINVAL; +} +EXPORT_SYMBOL(vhost_mdev_read); + + +ssize_t vhost_mdev_write(struct mdev_device *mdev, const char __user *buf, + size_t count, loff_t *ppos) +{ + return -EINVAL; +} +EXPORT_SYMBOL(vhost_mdev_write); + +int vhost_mdev_mmap(struct mdev_device *mdev, struct vm_area_struct *vma) +{ + // TODO + return -EINVAL; +} +EXPORT_SYMBOL(vhost_mdev_mmap); + +long vhost_mdev_ioctl(struct mdev_device *mdev, unsigned int cmd, + unsigned long arg) +{ + void __user *argp = (void __user *)arg; + struct vhost_mdev *vdpa; + unsigned long minsz; + int ret = 0; + + if (!mdev) + return -EINVAL; + + vdpa = mdev_get_drvdata(mdev); + if (!vdpa) + return -ENODEV; + + switch (cmd) { + case VFIO_DEVICE_GET_INFO: + { + struct vfio_device_info info; + + minsz = offsetofend(struct vfio_device_info, num_irqs); + + if (copy_from_user(&info, (void __user *)arg, minsz)) { + ret = -EFAULT; + break; + } + + if (info.argsz < minsz) { + ret = -EINVAL; + break; + } + + info.flags = VFIO_DEVICE_FLAGS_VHOST; + info.num_regions = 0; + info.num_irqs = 0; + + if (copy_to_user((void __user *)arg, &info, minsz)) { + ret = -EFAULT; + break; + } + + break; + } + case VFIO_DEVICE_GET_REGION_INFO: + case VFIO_DEVICE_GET_IRQ_INFO: + case VFIO_DEVICE_SET_IRQS: + case VFIO_DEVICE_RESET: + ret = -EINVAL; + break; + + case VHOST_MDEV_SET_STATE: + ret = vhost_set_state(vdpa, argp); + break; + case VHOST_GET_FEATURES: + ret = vhost_get_features(vdpa, argp); + break; + case VHOST_SET_FEATURES: + ret = vhost_set_features(vdpa, argp); + break; + case VHOST_GET_VRING_BASE: + ret = vhost_get_vring_base(vdpa, argp); + break; + default: + ret = vhost_dev_ioctl(&vdpa->dev, cmd, argp); + if (ret == -ENOIOCTLCMD) + ret = vhost_vring_ioctl(&vdpa->dev, cmd, argp); + } + + return ret; +} +EXPORT_SYMBOL(vhost_mdev_ioctl); + +int vhost_mdev_open(struct mdev_device *mdev) +{ + struct vhost_mdev *vdpa; + int ret = 0; + + vdpa = mdev_get_drvdata(mdev); + if (!vdpa) + return -ENODEV; + + mutex_lock(&vdpa->dev.mutex); + + if (vdpa->opened) + ret = -EBUSY; + else + vdpa->opened = true; + + mutex_unlock(&vdpa->dev.mutex); + + return ret; +} +EXPORT_SYMBOL(vhost_mdev_open); + +void vhost_mdev_close(struct mdev_device *mdev) +{ + struct vhost_mdev *vdpa; + + vdpa = mdev_get_drvdata(mdev); + + mutex_lock(&vdpa->dev.mutex); + + vhost_dev_stop(&vdpa->dev); + vhost_dev_cleanup(&vdpa->dev); + + vdpa->opened = false; + mutex_unlock(&vdpa->dev.mutex); +} +EXPORT_SYMBOL(vhost_mdev_close); + +/* + * Helpers for backend to set/get information. + */ + +int vhost_mdev_set_device_ops(struct vhost_mdev *vdpa, + const struct vhost_mdev_device_ops *ops) +{ + vdpa->ops = ops; + return 0; +} +EXPORT_SYMBOL(vhost_mdev_set_device_ops); + +int vhost_mdev_set_features(struct vhost_mdev *vdpa, u64 features) +{ + vdpa->features = features; + return 0; +} +EXPORT_SYMBOL(vhost_mdev_set_features); + +struct eventfd_ctx * +vhost_mdev_get_call_ctx(struct vhost_mdev *vdpa, int queue_id) +{ + return vdpa->vqs[queue_id].call_ctx; +} +EXPORT_SYMBOL(vhost_mdev_get_call_ctx); + +int vhost_mdev_get_acked_features(struct vhost_mdev *vdpa, u64 *features) +{ + *features = vdpa->acked_features; + return 0; +} +EXPORT_SYMBOL(vhost_mdev_get_acked_features); + +int vhost_mdev_get_vring_num(struct vhost_mdev *vdpa, int queue_id, u16 *num) +{ + *num = vdpa->vqs[queue_id].num; + return 0; +} +EXPORT_SYMBOL(vhost_mdev_get_vring_num); + +int vhost_mdev_get_vring_base(struct vhost_mdev *vdpa, int queue_id, u16 *base) +{ + *base = vdpa->vqs[queue_id].last_avail_idx; + return 0; +} +EXPORT_SYMBOL(vhost_mdev_get_vring_base); + +int vhost_mdev_get_vring_addr(struct vhost_mdev *vdpa, int queue_id, + struct vhost_vring_addr *addr) +{ + struct vhost_virtqueue *vq = &vdpa->vqs[queue_id]; + + /* + * XXX: we need userspace to pass guest physical address or + * IOVA directly. + */ + addr->flags = vq->log_used ? (0x1 << VHOST_VRING_F_LOG) : 0; + addr->desc_user_addr = (__u64)vq->desc; + addr->avail_user_addr = (__u64)vq->avail; + addr->used_user_addr = (__u64)vq->used; + addr->log_guest_addr = (__u64)vq->log_addr; + return 0; +} +EXPORT_SYMBOL(vhost_mdev_get_vring_addr); + +int vhost_mdev_get_log_base(struct vhost_mdev *vdpa, int queue_id, + void **log_base, u64 *log_size) +{ + // TODO + return 0; +} +EXPORT_SYMBOL(vhost_mdev_get_log_base); + +struct mdev_device *vhost_mdev_get_mdev(struct vhost_mdev *vdpa) +{ + return vdpa->mdev; +} +EXPORT_SYMBOL(vhost_mdev_get_mdev); + +void *vhost_mdev_get_private(struct vhost_mdev *vdpa) +{ + return vdpa->private; +} +EXPORT_SYMBOL(vhost_mdev_get_private); + +MODULE_VERSION("0.0.0"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Hardware vhost accelerator abstraction"); diff --git a/include/linux/vhost_mdev.h b/include/linux/vhost_mdev.h new file mode 100644 index 000000000000..070787ce6b36 --- /dev/null +++ b/include/linux/vhost_mdev.h @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2018-2019 Intel Corporation. + */ + +#ifndef _VHOST_MDEV_H +#define _VHOST_MDEV_H + +struct mdev_device; +struct vhost_mdev; + +typedef int (*vhost_mdev_start_device_t)(struct vhost_mdev *vdpa); +typedef int (*vhost_mdev_stop_device_t)(struct vhost_mdev *vdpa); +typedef int (*vhost_mdev_set_features_t)(struct vhost_mdev *vdpa); +typedef void (*vhost_mdev_notify_device_t)(struct vhost_mdev *vdpa, int queue_id); +typedef u64 (*vhost_mdev_get_notify_addr_t)(struct vhost_mdev *vdpa, int queue_id); +typedef u16 (*vhost_mdev_get_vring_base_t)(struct vhost_mdev *vdpa, int queue_id); +typedef void (*vhost_mdev_features_changed_t)(struct vhost_mdev *vdpa); + +struct vhost_mdev_device_ops { + vhost_mdev_start_device_t start; + vhost_mdev_stop_device_t stop; + vhost_mdev_notify_device_t notify; + vhost_mdev_get_notify_addr_t get_notify_addr; + vhost_mdev_get_vring_base_t get_vring_base; + vhost_mdev_features_changed_t features_changed; +}; + +struct vhost_mdev *vhost_mdev_alloc(struct mdev_device *mdev, + void *private, int nvqs); +void vhost_mdev_free(struct vhost_mdev *vdpa); + +ssize_t vhost_mdev_read(struct mdev_device *mdev, char __user *buf, + size_t count, loff_t *ppos); +ssize_t vhost_mdev_write(struct mdev_device *mdev, const char __user *buf, + size_t count, loff_t *ppos); +long vhost_mdev_ioctl(struct mdev_device *mdev, unsigned int cmd, + unsigned long arg); +int vhost_mdev_mmap(struct mdev_device *mdev, struct vm_area_struct *vma); +int vhost_mdev_open(struct mdev_device *mdev); +void vhost_mdev_close(struct mdev_device *mdev); + +int vhost_mdev_set_device_ops(struct vhost_mdev *vdpa, + const struct vhost_mdev_device_ops *ops); +int vhost_mdev_set_features(struct vhost_mdev *vdpa, u64 features); +struct eventfd_ctx *vhost_mdev_get_call_ctx(struct vhost_mdev *vdpa, + int queue_id); +int vhost_mdev_get_acked_features(struct vhost_mdev *vdpa, u64 *features); +int vhost_mdev_get_vring_num(struct vhost_mdev *vdpa, int queue_id, u16 *num); +int vhost_mdev_get_vring_base(struct vhost_mdev *vdpa, int queue_id, u16 *base); +int vhost_mdev_get_vring_addr(struct vhost_mdev *vdpa, int queue_id, + struct vhost_vring_addr *addr); +int vhost_mdev_get_log_base(struct vhost_mdev *vdpa, int queue_id, + void **log_base, u64 *log_size); +struct mdev_device *vhost_mdev_get_mdev(struct vhost_mdev *vdpa); +void *vhost_mdev_get_private(struct vhost_mdev *vdpa); + +#endif /* _VHOST_MDEV_H */ diff --git a/include/uapi/linux/vfio.h b/include/uapi/linux/vfio.h index 8f10748dac79..0300d6831cc5 100644 --- a/include/uapi/linux/vfio.h +++ b/include/uapi/linux/vfio.h @@ -201,6 +201,7 @@ struct vfio_device_info { #define VFIO_DEVICE_FLAGS_AMBA (1 << 3) /* vfio-amba device */ #define VFIO_DEVICE_FLAGS_CCW (1 << 4) /* vfio-ccw device */ #define VFIO_DEVICE_FLAGS_AP (1 << 5) /* vfio-ap device */ +#define VFIO_DEVICE_FLAGS_VHOST (1 << 6) /* vfio-vhost device */ __u32 num_regions; /* Max region index + 1 */ __u32 num_irqs; /* Max IRQ index + 1 */ }; @@ -217,6 +218,7 @@ struct vfio_device_info { #define VFIO_DEVICE_API_AMBA_STRING "vfio-amba" #define VFIO_DEVICE_API_CCW_STRING "vfio-ccw" #define VFIO_DEVICE_API_AP_STRING "vfio-ap" +#define VFIO_DEVICE_API_VHOST_STRING "vfio-vhost" /** * VFIO_DEVICE_GET_REGION_INFO - _IOWR(VFIO_TYPE, VFIO_BASE + 8, diff --git a/include/uapi/linux/vhost.h b/include/uapi/linux/vhost.h index 40d028eed645..5afbc2f08fa3 100644 --- a/include/uapi/linux/vhost.h +++ b/include/uapi/linux/vhost.h @@ -116,4 +116,12 @@ #define VHOST_VSOCK_SET_GUEST_CID _IOW(VHOST_VIRTIO, 0x60, __u64) #define VHOST_VSOCK_SET_RUNNING _IOW(VHOST_VIRTIO, 0x61, int) +/* VHOST_MDEV specific defines */ + +#define VHOST_MDEV_SET_STATE _IOW(VHOST_VIRTIO, 0x70, __u64) + +#define VHOST_MDEV_S_STOPPED 0 +#define VHOST_MDEV_S_RUNNING 1 +#define VHOST_MDEV_S_MAX 2 + #endif -- 2.17.1 _______________________________________________ Virtualization mailing list Virtualization@xxxxxxxxxxxxxxxxxxxxxxxxxx https://lists.linuxfoundation.org/mailman/listinfo/virtualization