This is for testing purpose to create virtio devices from user space. uvirtio-vga.c shows how to create a virtio-vga device. Currently we don't have a use case which requires user/kernel communication so read/write api hasn't been implemented. Signed-off-by: Lepton Wu <ytht.net@xxxxxxxxx> --- drivers/virtio/Kconfig | 8 + drivers/virtio/Makefile | 1 + drivers/virtio/uvirtio.c | 405 ++++++++++++++++++++++++++++++++++ include/linux/uvirtio.h | 8 + include/uapi/linux/uvirtio.h | 69 ++++++ samples/uvirtio/Makefile | 9 + samples/uvirtio/uvirtio-vga.c | 63 ++++++ 7 files changed, 563 insertions(+) create mode 100644 drivers/virtio/uvirtio.c create mode 100644 include/linux/uvirtio.h create mode 100644 include/uapi/linux/uvirtio.h create mode 100644 samples/uvirtio/Makefile create mode 100644 samples/uvirtio/uvirtio-vga.c diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig index 69a32dfc318a..8bfb77db4efa 100644 --- a/drivers/virtio/Kconfig +++ b/drivers/virtio/Kconfig @@ -109,4 +109,12 @@ config VIRTIO_MMIO_CMDLINE_DEVICES If unsure, say 'N'. +config UVIRTIO + tristate "UVirtio driver" + select VIRTIO + ---help--- + This driver supports create virtio devices from guest userspace. + + If unsure, say 'N'. + endif # VIRTIO_MENU diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile index 29a1386ecc03..558b2f890e8c 100644 --- a/drivers/virtio/Makefile +++ b/drivers/virtio/Makefile @@ -7,3 +7,4 @@ virtio_pci-$(CONFIG_VIRTIO_PCI_LEGACY) += virtio_pci_legacy.o obj-$(CONFIG_VIRTIO_BALLOON) += virtio_balloon.o obj-$(CONFIG_VIRTIO_INPUT) += virtio_input.o obj-$(CONFIG_VIRTIO_VDPA) += virtio_vdpa.o +obj-$(CONFIG_UVIRTIO) += uvirtio.o diff --git a/drivers/virtio/uvirtio.c b/drivers/virtio/uvirtio.c new file mode 100644 index 000000000000..a25107473dab --- /dev/null +++ b/drivers/virtio/uvirtio.c @@ -0,0 +1,405 @@ +/* + * User level driver support for virtio subsystem + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/miscdevice.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/uvirtio.h> +#include <linux/virtio.h> +#include <linux/virtio_config.h> +#include <linux/virtio_ring.h> +#include <uapi/linux/uvirtio.h> + +#define UVIRTIO_MAX_EXPECT_DATA (1UL << 20) + +struct uvirtio_device { + struct virtio_device vdev; + struct mutex mutex; + enum uvirtio_state state; + unsigned char virtio_status; + struct uvirtio_setup setup; + struct uvirtio_expect expect; + char *expect_data; +}; + +static struct miscdevice uvirtio_misc; + +static struct bus_type uvirtio_bus = { + .name = "", +}; + +static u64 uvirtio_get_features(struct virtio_device *dev) +{ + struct uvirtio_device *udev = container_of(dev, struct uvirtio_device, + vdev); + return udev->setup.features; +} + +static int uvirtio_finalize_features(struct virtio_device *vdev) +{ + return 0; +} + +static void uvirtio_get(struct virtio_device *dev, unsigned int offset, + void *buf, unsigned int len) +{ + struct uvirtio_device *udev = container_of(dev, struct uvirtio_device, + vdev); + if (WARN_ON(offset + len > udev->setup.config_len)) + return; + memcpy(buf, (char *)udev->setup.config_addr + offset, len); +} + +static u8 uvirtio_get_status(struct virtio_device *dev) +{ + struct uvirtio_device *udev = container_of(dev, struct uvirtio_device, + vdev); + return udev->virtio_status; +} + +static void uvirtio_set_status(struct virtio_device *dev, u8 status) +{ + struct uvirtio_device *udev = container_of(dev, struct uvirtio_device, + vdev); + if (WARN_ON(!status)) + return; + udev->virtio_status = status; +} + +static int find_match(int write, char *buf, unsigned int len, + struct uvirtio_block *block, char *data) +{ + int i; + int off = 0; + for (i = 0; i < UVIRTIO_MAX_RULES; ++i) { + if (!block->rules[i].len) + break; + if (block->rules[i].off + block->rules[i].len > len) + return -1; + if (write) { + memcpy(buf + block->rules[i].off, + data + block->data + off, block->rules[i].len); + } else { + if (memcmp(buf + block->rules[i].off, + data + block->data + off, + block->rules[i].len)) + return -1; + } + off += block->rules[i].len; + } + return i ? write : 0; +} + +static void process_vq(struct virtio_device *vdev, const char *name, + struct vring_desc *desc, int idx) +{ + unsigned short flags; + char *buf; + unsigned int len; + struct uvirtio_device *udev = container_of(vdev, struct uvirtio_device, + vdev); + struct uvirtio_expect *expect = &udev->expect; + int i, j, matched = 0; + + for (i = 0; i < UVIRTIO_MAX_EXPECTS; ++i) { + if (!expect->expects[i].vq_name[0]) + break; + if (strncmp(name, expect->expects[i].vq_name, + sizeof(expect->expects[i].vq_name))) + continue; + for (j = 0; j < UVIRTIO_MAX_BLOCKS && + expect->expects[i].blocks[j].len; ++j) { + flags = virtio16_to_cpu(vdev, desc[idx].flags); + len = virtio32_to_cpu(vdev, desc[idx].len); + buf = __va(virtio64_to_cpu(vdev, desc[idx].addr)); + if (expect->expects[i].blocks[j].len != len || + expect->expects[i].blocks[j].flags != flags) + break; + matched = find_match(flags & VRING_DESC_F_WRITE, + buf, len, + &expect->expects[i].blocks[j], + udev->expect_data); + if (matched) + break; + if (!(flags & VRING_DESC_F_NEXT)) + break; + idx = virtio16_to_cpu(vdev, desc[idx].next); + } + if (matched > 0) + break; + } +} + +static bool uvirtio_notify(struct virtqueue *vq) +{ + struct vring *r = (struct vring *)(vq + 1); + int used_idx, avail_idx, id; + used_idx = virtio16_to_cpu(vq->vdev, r->used->idx); + avail_idx = virtio16_to_cpu(vq->vdev, r->avail->idx); + while (used_idx != avail_idx) { + id = used_idx & (r->num - 1); + process_vq(vq->vdev, vq->name, r->desc, r->avail->ring[id]); + r->used->ring[id].id = r->avail->ring[id]; + used_idx++; + } + r->used->idx = r->avail->idx; + vq->callback(vq); + return true; +} + +static int uvirtio_find_vqs(struct virtio_device *dev, unsigned int nvqs, + struct virtqueue *vqs[], + vq_callback_t * callbacks[], + const char *const names[], + const bool *ctx, struct irq_affinity *desc) +{ + int i, j; + + for (i = 0; i < nvqs; ++i) { + vqs[i] = vring_create_virtqueue(i, 256, SMP_CACHE_BYTES, dev, + true, false, false, + uvirtio_notify, callbacks[i], + names[i]); + if (!vqs[i]) + goto err; + } + return 0; +err: + for (j = 0; j < i; ++j) { + vring_del_virtqueue(vqs[j]); + vqs[j] = NULL; + } + return -ENOMEM; +} + +static void uvirtio_del_vqs(struct virtio_device *dev) +{ + struct virtqueue *vq, *n; + + list_for_each_entry_safe(vq, n, &dev->vqs, list) + vring_del_virtqueue(vq); +} + +static void uvirtio_reset(struct virtio_device *vdev) +{ +} + +static const struct virtio_config_ops uvirtio_vq_ops = { + .get_features = uvirtio_get_features, + .finalize_features = uvirtio_finalize_features, + .get = uvirtio_get, + .get_status = uvirtio_get_status, + .set_status = uvirtio_set_status, + .reset = uvirtio_reset, + .find_vqs = uvirtio_find_vqs, + .del_vqs = uvirtio_del_vqs, +}; + +static int uvirtio_open(struct inode *inode, struct file *file) +{ + struct uvirtio_device *newdev; + + newdev = kzalloc(sizeof(*newdev), GFP_KERNEL); + if (!newdev) + return -ENOMEM; + + mutex_init(&newdev->mutex); + newdev->state = UVST_NEW_DEVICE; + + file->private_data = newdev; + nonseekable_open(inode, file); + + return 0; +} + +static int uvirtio_release(struct inode *inode, struct file *file) +{ + struct uvirtio_device *udev = file->private_data; + + if (udev->state == UVST_CREATED) { + if (udev->setup.flags & (1 << UVIRTIO_DEV_FLAG_STICK)) + return 0; + unregister_virtio_device(&udev->vdev); + } + kfree((void *)udev->setup.config_addr); + kfree(udev->expect_data); + kfree(udev); + return 0; +} + +static int load_data(struct uvirtio_block *b, char **ptr, + unsigned int *off, unsigned int *len) +{ + int i, ret, total = 0; + void __user *p = (void __user *)b->data; + unsigned int need; + int realloc = 0; + + for (i = 0; i < UVIRTIO_MAX_RULES; ++i) { + if (!b->rules[i].len) + break; + total += b->rules[i].len; + } + if (!total) { + return 0; + } + + need = (*off) + total; + while (need > (*len)) { + if (*len >= UVIRTIO_MAX_EXPECT_DATA) + return -ENOMEM; + *len = (*len) << 1; + realloc = 1; + } + if (realloc) + (*ptr) = krealloc(*ptr, *len, GFP_KERNEL); + if (!(*ptr)) + return -ENOMEM; + + ret = copy_from_user(*ptr + (*off), p, total); + if (ret) + return ret; + b->data = (*off); + (*off) += total; + return 0; +} + +static int set_expect(struct uvirtio_device *udev, unsigned long arg) +{ + int i, j, ret; + struct uvirtio_block *b; + unsigned int off = 0, len = 1024; + + ret = copy_from_user(&udev->expect, (void __user *)arg, + sizeof(udev->expect)); + if (ret) + return ret; + + udev->expect_data = kmalloc(len, GFP_KERNEL); + + if (!udev->expect_data) + return -ENOMEM; + + for (i = 0; i < UVIRTIO_MAX_EXPECTS; ++i) { + if (!udev->expect.expects[i].vq_name[0]) + break; + for (j = 0; j < UVIRTIO_MAX_BLOCKS; ++j) { + b = &udev->expect.expects[i].blocks[j]; + if (!b->len) + break; + ret = load_data(b, &udev->expect_data, &off, &len); + if (ret) + goto err; + } + } + return 0; +err: + kfree(udev->expect_data); + udev->expect_data = NULL; + return ret; +} + +static int create_device(struct uvirtio_device *udev, unsigned long arg) +{ + int ret; + char *config = NULL; + ret = copy_from_user(&udev->setup, (void __user *)arg, + sizeof(udev->setup)); + if (ret) + return ret; + if (!udev->setup.config_len || !udev->setup.config_addr) + return -EINVAL; + if (udev->setup.config_len > UVIRTIO_MAX_CONFIG_LEN) + return -EINVAL; + if (udev->setup.flags & ~((1 << UVIRTIO_DEV_FLAG_MAX) - 1)) + return -EINVAL; + config = kmalloc(udev->setup.config_len, GFP_KERNEL); + if (!config) + return -ENOMEM; + ret = copy_from_user(config, (void __user *)udev->setup.config_addr, + udev->setup.config_len); + if (ret) + goto err; + udev->setup.config_addr = (uint64_t) config; + if (uvirtio_misc.this_device->bus == NULL) { + uvirtio_misc.this_device->bus = &uvirtio_bus; + } + udev->vdev.dev.parent = uvirtio_misc.this_device; + udev->vdev.id.device = udev->setup.id; + udev->vdev.config = &uvirtio_vq_ops; + ret = register_virtio_device(&udev->vdev); + if (ret) + goto err; + udev->state = UVST_CREATED; + return 0; +err: + kfree(config); + udev->setup.config_addr = 0; + return ret; +} + +static long uvirtio_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct uvirtio_device *udev = file->private_data; + int ret; + + ret = mutex_lock_interruptible(&udev->mutex); + if (ret) + return ret; + + if (udev->state != UVST_NEW_DEVICE) { + ret = -EINVAL; + goto out; + } + + switch (cmd) { + case UV_DEV_CREATE: + ret = create_device(udev, arg); + break; + case UV_DEV_EXPECT: + ret = set_expect(udev, arg); + break; + default: + ret = -EINVAL; + break; + } +out: + mutex_unlock(&udev->mutex); + return ret; +} + +static const struct file_operations uvirtio_fops = { + .owner = THIS_MODULE, + .open = uvirtio_open, + .release = uvirtio_release, + .unlocked_ioctl = uvirtio_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = uvirtio_ioctl, +#endif + .llseek = no_llseek, +}; + +static struct miscdevice uvirtio_misc = { + .fops = &uvirtio_fops, + .minor = MISC_DYNAMIC_MINOR, + .name = "uvirtio", +}; + +module_misc_device(uvirtio_misc); + +MODULE_VERSION("0.0.1"); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("User level driver support for virtio subsystem"); diff --git a/include/linux/uvirtio.h b/include/linux/uvirtio.h new file mode 100644 index 000000000000..ff217080d921 --- /dev/null +++ b/include/linux/uvirtio.h @@ -0,0 +1,8 @@ +#ifndef __UVIRTIO_H_ +#define __UVIRTIO_H_ + +#include <uapi/linux/uvirtio.h> + +enum uvirtio_state { UVST_NEW_DEVICE, UVST_CREATED }; + +#endif /* __UVIRTIO_H_ */ diff --git a/include/uapi/linux/uvirtio.h b/include/uapi/linux/uvirtio.h new file mode 100644 index 000000000000..4771e527fce0 --- /dev/null +++ b/include/uapi/linux/uvirtio.h @@ -0,0 +1,69 @@ +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */ +/* + * User level driver support for virtio subsystem + * + * Based on uinput.c by Aristeu Sergio Rozanski Filho + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +#ifndef _UAPI_UVIRTIO_H_ +#define _UAPI_UVIRTIO_H_ + +#include <linux/types.h> + +/* ioctl */ + +#define UVIRTIO_MAX_CONFIG_LEN 1024 +#define UVIRTIO_MAX_RULES 8 +#define UVIRTIO_MAX_BLOCKS 8 +#define UVIRTIO_MAX_EXPECTS 8 +#define UVIRTIO_MAX_VQ_NAME 64 + +enum { + UVIRTIO_DEV_FLAG_STICK = 0, + UVIRTIO_DEV_FLAG_MAX +}; + +struct uvirtio_setup { + __u64 features; + __u64 config_addr; + __u16 config_len; + __u16 flags; + __u32 id; +}; + +struct uvirtio_block { + __u32 len; + __u16 flags; + __u16 unused; + __u64 data; + struct { + __u16 off; + __u16 len; + } rules[UVIRTIO_MAX_RULES]; +}; + +struct uvirtio_expect { + struct { + char vq_name[UVIRTIO_MAX_VQ_NAME]; + struct uvirtio_block blocks[UVIRTIO_MAX_BLOCKS]; + } expects[UVIRTIO_MAX_EXPECTS]; +}; + +#define UVIRTIO_IOCTL_BASE 'V' +#define UV_DEV_CREATE _IOW(UVIRTIO_IOCTL_BASE, 1, struct uvirtio_setup) +#define UV_DEV_EXPECT _IOW(UVIRTIO_IOCTL_BASE, 2, struct uvirtio_expect) + +#endif /* _UAPI_UVIRTIO_H_ */ diff --git a/samples/uvirtio/Makefile b/samples/uvirtio/Makefile new file mode 100644 index 000000000000..ee830986eda2 --- /dev/null +++ b/samples/uvirtio/Makefile @@ -0,0 +1,9 @@ +uvirtio-vga: uvirtio-vga.c uapi/linux/uvirtio.h linux/uvirtio.h + $(CC) -o $@ $^ -I. +uapi/linux/uvirtio.h: + mkdir -p uapi/linux && ln ../../../../include/uapi/linux/uvirtio.h uapi/linux -sf +linux/uvirtio.h: + mkdir -p linux && ln ../../../include/linux/uvirtio.h linux -sf +.PHONY: clean +clean: + rm -f uvirtio-vga diff --git a/samples/uvirtio/uvirtio-vga.c b/samples/uvirtio/uvirtio-vga.c new file mode 100644 index 000000000000..b65cb62a6d91 --- /dev/null +++ b/samples/uvirtio/uvirtio-vga.c @@ -0,0 +1,63 @@ +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <linux/uvirtio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <linux/virtio_config.h> +#include <linux/virtio_ids.h> +#include <linux/virtio_gpu.h> +#include <linux/virtio_ring.h> + +void error(char *str) +{ + fprintf(stderr, "%s\n", str); + exit(-1); +} + +int main() +{ + int fd = open("/dev/uvirtio", O_RDWR); + if (fd < 0) { + error("open"); + } + struct uvirtio_expect expect = { }; + + strcpy(expect.expects[0].vq_name, "control"); + struct uvirtio_block *b = &expect.expects[0].blocks[0]; + struct virtio_gpu_ctrl_hdr cmd; + cmd.type = VIRTIO_GPU_CMD_GET_DISPLAY_INFO; + b->len = sizeof(cmd); + b->flags = VRING_DESC_F_NEXT; + b->data = (__u64) & cmd.type; + b->rules[0].off = offsetof(struct virtio_gpu_ctrl_hdr, type); + b->rules[0].len = sizeof(cmd.type); + struct virtio_gpu_resp_display_info info = { }; + info.pmodes[0].r.width = 1024; + info.pmodes[0].r.height = 768; + info.pmodes[0].enabled = 1; + b = &expect.expects[0].blocks[1]; + b->len = sizeof(info); + b->flags = VRING_DESC_F_WRITE; + b->data = (__u64) & info.pmodes[0]; + b->rules[0].off = + offsetof(struct virtio_gpu_resp_display_info, pmodes[0]); + b->rules[0].len = sizeof(info.pmodes[0]); + int ret = ioctl(fd, UV_DEV_EXPECT, &expect); + if (ret < 0) + error("ioctl UV_DEV_EXPECT"); + struct uvirtio_setup setup; + struct virtio_gpu_config config = {.num_scanouts = 1 }; + setup.features = 1ULL << VIRTIO_F_VERSION_1; + setup.config_addr = (__u64) & config; + setup.config_len = sizeof(config); + setup.id = VIRTIO_ID_GPU; + setup.flags = 1 << UVIRTIO_DEV_FLAG_STICK; + ret = ioctl(fd, UV_DEV_CREATE, &setup); + if (ret < 0) + error("ioctl UV_DEV_CREATE"); +} -- 2.26.2.303.gf8c07b1a785-goog _______________________________________________ Virtualization mailing list Virtualization@xxxxxxxxxxxxxxxxxxxxxxxxxx https://lists.linuxfoundation.org/mailman/listinfo/virtualization