1413 lines
36 KiB
Diff
1413 lines
36 KiB
Diff
From ee3ae46ee497e111f6f4afbe3635f461e991c1e9 Mon Sep 17 00:00:00 2001
|
|
From: Zach Reizner <zachr@google.com>
|
|
Date: Mon, 14 Aug 2017 17:16:55 -0700
|
|
Subject: [PATCH] virtwl: add virtwl driver
|
|
|
|
TEST=emerge-tatl chromeos-kernel-4_4
|
|
BUG=chromium:738638
|
|
|
|
Change-Id: I6e8e128a5548c915a9561938cbb066edc8c42747
|
|
Reviewed-on: https://chromium-review.googlesource.com/567299
|
|
Commit-Ready: Zach Reizner <zachr@chromium.org>
|
|
Tested-by: Zach Reizner <zachr@chromium.org>
|
|
Reviewed-by: Zach Reizner <zachr@chromium.org>
|
|
---
|
|
|
|
diff --git a/drivers/virtio/Kconfig b/drivers/virtio/Kconfig
|
|
index cab9f3f..968e737 100644
|
|
--- a/drivers/virtio/Kconfig
|
|
+++ b/drivers/virtio/Kconfig
|
|
@@ -79,4 +79,12 @@
|
|
|
|
If unsure, say 'N'.
|
|
|
|
+config VIRTIO_WL
|
|
+ bool "Virtio Wayland driver"
|
|
+ depends on VIRTIO
|
|
+ ---help---
|
|
+ This driver supports proxying of a wayland socket from host to guest.
|
|
+
|
|
+ If unsure, say 'N'.
|
|
+
|
|
endmenu
|
|
diff --git a/drivers/virtio/Makefile b/drivers/virtio/Makefile
|
|
index 41e30e3..bd941ca 100644
|
|
--- a/drivers/virtio/Makefile
|
|
+++ b/drivers/virtio/Makefile
|
|
@@ -5,3 +5,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_WL) += virtio_wl.o
|
|
diff --git a/drivers/virtio/virtio_wl.c b/drivers/virtio/virtio_wl.c
|
|
new file mode 100644
|
|
index 0000000..6cec6ae
|
|
--- /dev/null
|
|
+++ b/drivers/virtio/virtio_wl.c
|
|
@@ -0,0 +1,1198 @@
|
|
+/*
|
|
+ * Wayland Virtio Driver
|
|
+ * Copyright (C) 2017 Google, Inc.
|
|
+ *
|
|
+ * 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.
|
|
+ *
|
|
+ */
|
|
+
|
|
+/*
|
|
+Virtio Wayland (virtio_wl or virtwl) is a virtual device that allows a guest
|
|
+virtual machine to use a wayland server on the host transparently (to the host).
|
|
+This is done by proxying the wayland protocol socket stream verbatim between the
|
|
+host and guest over 2 (recv and send) virtio queues. The guest can request new
|
|
+wayland server connections to give each guest wayland client a different server
|
|
+context. Each host connection's file descriptor is exposed to the guest as a
|
|
+virtual file descriptor (VFD). Additionally, the guest can request shared memory
|
|
+file descriptors which are also exposed as VFDs. These shared memory VFDs are
|
|
+directly writable by the guest via device memory injected by the host. Each VFD
|
|
+is sendable along a connection context VFD and will appear as ancillary data to
|
|
+the wayland server, just like a message from an ordinary wayland client. When
|
|
+the wayland server sends a shared memory file descriptor to the client (such as
|
|
+when sending a keymap), a VFD is allocated by the device automatically and its
|
|
+memory is injected into as device memory.
|
|
+
|
|
+This driver is intended to be paired with the `virtwl_guest_proxy` program which
|
|
+is run in the guest system and acts like a wayland server. It accepts wayland
|
|
+client connections and converts their socket messages to ioctl messages exposed
|
|
+by this driver via the `/dev/wl` device file. While it would be possible to
|
|
+expose a unix stream socket from this driver, the user space helper is much
|
|
+cleaner to write.
|
|
+*/
|
|
+
|
|
+#include <linux/anon_inodes.h>
|
|
+#include <linux/cdev.h>
|
|
+#include <linux/completion.h>
|
|
+#include <linux/err.h>
|
|
+#include <linux/fdtable.h>
|
|
+#include <linux/file.h>
|
|
+#include <linux/fs.h>
|
|
+#include <linux/idr.h>
|
|
+#include <linux/kfifo.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/mutex.h>
|
|
+#include <linux/poll.h>
|
|
+#include <linux/scatterlist.h>
|
|
+#include <linux/syscalls.h>
|
|
+#include <linux/uaccess.h>
|
|
+#include <linux/virtio.h>
|
|
+#include <linux/virtio_wl.h>
|
|
+
|
|
+#define VFD_ILLEGAL_SIGN_BIT 0x80000000
|
|
+#define VFD_HOST_VFD_ID_BIT 0x40000000
|
|
+
|
|
+struct virtwl_vfd_qentry {
|
|
+ struct list_head list;
|
|
+ struct virtio_wl_ctrl_hdr *hdr;
|
|
+ unsigned int len; /* total byte length of ctrl_vfd_* + vfds + data */
|
|
+ unsigned int vfd_offset; /* int offset into vfds */
|
|
+ unsigned int data_offset; /* byte offset into data */
|
|
+};
|
|
+
|
|
+struct virtwl_vfd {
|
|
+ struct kobject kobj;
|
|
+ struct mutex lock;
|
|
+
|
|
+ struct virtwl_info *vi;
|
|
+ uint32_t id;
|
|
+ uint32_t flags;
|
|
+ uint64_t pfn;
|
|
+ uint32_t size;
|
|
+
|
|
+ struct list_head in_queue; /* list of virtwl_vfd_qentry */
|
|
+ wait_queue_head_t in_waitq;
|
|
+};
|
|
+
|
|
+struct virtwl_info {
|
|
+ dev_t dev_num;
|
|
+ struct device *dev;
|
|
+ struct class *class;
|
|
+ struct cdev cdev;
|
|
+
|
|
+ struct mutex vq_locks[VIRTWL_QUEUE_COUNT];
|
|
+ struct virtqueue *vqs[VIRTWL_QUEUE_COUNT];
|
|
+ struct work_struct in_vq_work;
|
|
+ struct work_struct out_vq_work;
|
|
+
|
|
+ wait_queue_head_t out_waitq;
|
|
+
|
|
+ struct mutex vfds_lock;
|
|
+ struct idr vfds;
|
|
+};
|
|
+
|
|
+static struct virtwl_vfd *virtwl_vfd_alloc(struct virtwl_info *vi);
|
|
+static void virtwl_vfd_free(struct virtwl_vfd *vfd);
|
|
+
|
|
+static struct file_operations virtwl_vfd_fops;
|
|
+
|
|
+static int virtwl_resp_err(unsigned int type)
|
|
+{
|
|
+ switch (type) {
|
|
+ case VIRTIO_WL_RESP_OK:
|
|
+ case VIRTIO_WL_RESP_VFD_NEW:
|
|
+ return 0;
|
|
+ case VIRTIO_WL_RESP_ERR:
|
|
+ return -ENODEV; /* Device is no longer reliable */
|
|
+ case VIRTIO_WL_RESP_OUT_OF_MEMORY:
|
|
+ return -ENOMEM;
|
|
+ case VIRTIO_WL_RESP_INVALID_ID:
|
|
+ case VIRTIO_WL_RESP_INVALID_TYPE:
|
|
+ default:
|
|
+ return -EINVAL;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int vq_return_inbuf_locked(struct virtqueue *vq, void *buffer)
|
|
+{
|
|
+ int ret;
|
|
+ struct scatterlist sg[1];
|
|
+ sg_init_one(sg, buffer, PAGE_SIZE);
|
|
+
|
|
+ ret = virtqueue_add_inbuf(vq, sg, 1, buffer, GFP_KERNEL);
|
|
+ if (ret) {
|
|
+ printk("virtwl: failed to give inbuf to host: %d\n", ret);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int vq_queue_out(struct virtwl_info *vi, struct scatterlist *out_sg,
|
|
+ struct scatterlist *in_sg,
|
|
+ struct completion *finish_completion,
|
|
+ bool nonblock)
|
|
+{
|
|
+ struct virtqueue *vq = vi->vqs[VIRTWL_VQ_OUT];
|
|
+ struct mutex *vq_lock = &vi->vq_locks[VIRTWL_VQ_OUT];
|
|
+ struct scatterlist *sgs[] = { out_sg, in_sg };
|
|
+ int ret = 0;
|
|
+
|
|
+ mutex_lock(vq_lock);
|
|
+ while ((ret = virtqueue_add_sgs(vq, sgs, 1, 1, finish_completion,
|
|
+ GFP_KERNEL)) == -ENOSPC) {
|
|
+ mutex_unlock(vq_lock);
|
|
+ if (nonblock)
|
|
+ return -EAGAIN;
|
|
+ if (!wait_event_timeout(vi->out_waitq, vq->num_free > 0, HZ))
|
|
+ return -EBUSY;
|
|
+ mutex_lock(vq_lock);
|
|
+ }
|
|
+ if (!ret)
|
|
+ virtqueue_kick(vq);
|
|
+ mutex_unlock(vq_lock);
|
|
+
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static int vq_fill_locked(struct virtqueue *vq)
|
|
+{
|
|
+ void *buffer;
|
|
+ int ret = 0;
|
|
+
|
|
+ while (vq->num_free > 0) {
|
|
+ buffer = kmalloc(PAGE_SIZE, GFP_KERNEL);
|
|
+ if (!buffer) {
|
|
+ ret = -ENOMEM;
|
|
+ goto clear_queue;
|
|
+ }
|
|
+
|
|
+ ret = vq_return_inbuf_locked(vq, buffer);
|
|
+ if (ret)
|
|
+ goto clear_queue;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+clear_queue:
|
|
+ while ((buffer = virtqueue_detach_unused_buf(vq))) {
|
|
+ kfree(buffer);
|
|
+ }
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static bool vq_handle_new(struct virtwl_info *vi,
|
|
+ struct virtio_wl_ctrl_vfd_new *new, unsigned int len)
|
|
+{
|
|
+ struct virtwl_vfd *vfd;
|
|
+ u32 id = new->vfd_id;
|
|
+ int ret;
|
|
+
|
|
+ if (id == 0)
|
|
+ return true; /* return the inbuf to vq */
|
|
+
|
|
+ if (!(id & VFD_HOST_VFD_ID_BIT) || (id & VFD_ILLEGAL_SIGN_BIT)) {
|
|
+ printk("virtwl: received a vfd with invalid id: %u\n", id);
|
|
+ return true; /* return the inbuf to vq */
|
|
+ }
|
|
+
|
|
+ vfd = virtwl_vfd_alloc(vi);
|
|
+ if (!vfd)
|
|
+ return true; /* return the inbuf to vq */
|
|
+
|
|
+ mutex_lock(&vi->vfds_lock);
|
|
+ ret = idr_alloc(&vi->vfds, vfd, id, id + 1, GFP_KERNEL);
|
|
+ mutex_unlock(&vi->vfds_lock);
|
|
+
|
|
+ if (ret <= 0) {
|
|
+ virtwl_vfd_free(vfd);
|
|
+ printk("virtwl: failed to place received vfd: %d\n", ret);
|
|
+ return true; /* return the inbuf to vq */
|
|
+ }
|
|
+
|
|
+ vfd->id = id;
|
|
+ vfd->size = new->size;
|
|
+ vfd->pfn = new->pfn;
|
|
+ vfd->flags = new->flags;
|
|
+
|
|
+ return true; /* return the inbuf to vq */
|
|
+}
|
|
+
|
|
+static bool vq_handle_recv(struct virtwl_info *vi,
|
|
+ struct virtio_wl_ctrl_vfd_recv *recv,
|
|
+ unsigned int len)
|
|
+{
|
|
+ struct virtwl_vfd *vfd;
|
|
+ struct virtwl_vfd_qentry *qentry;
|
|
+
|
|
+ mutex_lock(&vi->vfds_lock);
|
|
+ vfd = idr_find(&vi->vfds, recv->vfd_id);
|
|
+ if (vfd)
|
|
+ mutex_lock(&vfd->lock);
|
|
+ mutex_unlock(&vi->vfds_lock);
|
|
+
|
|
+ if (!vfd) {
|
|
+ printk("virtwl: recv for unknown vfd_id %u\n", recv->vfd_id);
|
|
+ return true; /* return the inbuf to vq */
|
|
+ }
|
|
+
|
|
+ qentry = kzalloc(sizeof(*qentry), GFP_KERNEL);
|
|
+ if (!qentry) {
|
|
+ mutex_unlock(&vfd->lock);
|
|
+ printk("virtwl: failed to allocate qentry for vfd\n");
|
|
+ return true; /* return the inbuf to vq */
|
|
+ }
|
|
+
|
|
+ qentry->hdr = &recv->hdr;
|
|
+ qentry->len = len;
|
|
+
|
|
+ list_add_tail(&qentry->list, &vfd->in_queue);
|
|
+ wake_up_interruptible(&vfd->in_waitq);
|
|
+ mutex_unlock(&vfd->lock);
|
|
+
|
|
+ return false; /* no return the inbuf to vq */
|
|
+}
|
|
+
|
|
+static bool vq_dispatch_hdr(struct virtwl_info *vi, unsigned int len,
|
|
+ struct virtio_wl_ctrl_hdr *hdr)
|
|
+{
|
|
+ struct virtqueue *vq = vi->vqs[VIRTWL_VQ_IN];
|
|
+ struct mutex *vq_lock = &vi->vq_locks[VIRTWL_VQ_IN];
|
|
+ bool return_vq = true;
|
|
+ int ret;
|
|
+
|
|
+ switch (hdr->type) {
|
|
+ case VIRTIO_WL_CMD_VFD_NEW:
|
|
+ return_vq = vq_handle_new(vi,
|
|
+ (struct virtio_wl_ctrl_vfd_new *)hdr,
|
|
+ len);
|
|
+ break;
|
|
+ case VIRTIO_WL_CMD_VFD_RECV:
|
|
+ return_vq = vq_handle_recv(vi,
|
|
+ (struct virtio_wl_ctrl_vfd_recv *)hdr, len);
|
|
+ break;
|
|
+ default:
|
|
+ printk("virtwl: unhandled ctrl command: %u\n", hdr->type);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (!return_vq)
|
|
+ return false; /* no kick the vq */
|
|
+
|
|
+ mutex_lock(vq_lock);
|
|
+ ret = vq_return_inbuf_locked(vq, hdr);
|
|
+ mutex_unlock(vq_lock);
|
|
+ if (ret) {
|
|
+ printk("virtwl: failed to return inbuf to host: %d\n", ret);
|
|
+ kfree(hdr);
|
|
+ }
|
|
+
|
|
+ return true; /* kick the vq */
|
|
+}
|
|
+
|
|
+static void vq_in_work_handler(struct work_struct *work)
|
|
+{
|
|
+ struct virtwl_info *vi = container_of(work, struct virtwl_info,
|
|
+ in_vq_work);
|
|
+ struct virtqueue *vq = vi->vqs[VIRTWL_VQ_IN];
|
|
+ struct mutex *vq_lock = &vi->vq_locks[VIRTWL_VQ_IN];
|
|
+ void *buffer;
|
|
+ unsigned int len;
|
|
+ bool kick_vq = false;
|
|
+
|
|
+ mutex_lock(vq_lock);
|
|
+ while ((buffer = virtqueue_get_buf(vq, &len)) != NULL) {
|
|
+ struct virtio_wl_ctrl_hdr *hdr = buffer;
|
|
+ mutex_unlock(vq_lock);
|
|
+ kick_vq |= vq_dispatch_hdr(vi, len, hdr);
|
|
+ mutex_lock(vq_lock);
|
|
+ }
|
|
+ mutex_unlock(vq_lock);
|
|
+
|
|
+ if (kick_vq)
|
|
+ virtqueue_kick(vq);
|
|
+}
|
|
+
|
|
+static void vq_out_work_handler(struct work_struct *work)
|
|
+{
|
|
+ struct virtwl_info *vi = container_of(work, struct virtwl_info,
|
|
+ out_vq_work);
|
|
+ struct virtqueue *vq = vi->vqs[VIRTWL_VQ_OUT];
|
|
+ struct mutex *vq_lock = &vi->vq_locks[VIRTWL_VQ_OUT];
|
|
+ unsigned int len;
|
|
+ struct completion *finish_completion;
|
|
+ bool wake_waitq = false;
|
|
+
|
|
+ mutex_lock(vq_lock);
|
|
+ while ((finish_completion = virtqueue_get_buf(vq, &len)) != NULL) {
|
|
+ wake_waitq = true;
|
|
+ complete(finish_completion);
|
|
+ }
|
|
+ mutex_unlock(vq_lock);
|
|
+
|
|
+ if (wake_waitq)
|
|
+ wake_up_interruptible(&vi->out_waitq);
|
|
+}
|
|
+
|
|
+static void vq_in_cb(struct virtqueue *vq)
|
|
+{
|
|
+ struct virtwl_info *vi = vq->vdev->priv;
|
|
+ schedule_work(&vi->in_vq_work);
|
|
+}
|
|
+
|
|
+static void vq_out_cb(struct virtqueue *vq)
|
|
+{
|
|
+ struct virtwl_info *vi = vq->vdev->priv;
|
|
+ schedule_work(&vi->out_vq_work);
|
|
+}
|
|
+
|
|
+static struct virtwl_vfd *virtwl_vfd_alloc(struct virtwl_info *vi)
|
|
+{
|
|
+ struct virtwl_vfd *vfd = kzalloc(sizeof(struct virtwl_vfd), GFP_KERNEL);
|
|
+ if (!vfd)
|
|
+ return ERR_PTR(-ENOMEM);
|
|
+
|
|
+ vfd->vi = vi;
|
|
+
|
|
+ mutex_init(&vfd->lock);
|
|
+ INIT_LIST_HEAD(&vfd->in_queue);
|
|
+ init_waitqueue_head(&vfd->in_waitq);
|
|
+
|
|
+ return vfd;
|
|
+}
|
|
+
|
|
+/* Locks the vfd and unlinks its id from vi */
|
|
+static void virtwl_vfd_lock_unlink(struct virtwl_vfd *vfd)
|
|
+{
|
|
+ struct virtwl_info *vi = vfd->vi;
|
|
+ /* this order is important to avoid deadlock */
|
|
+ mutex_lock(&vi->vfds_lock);
|
|
+ mutex_lock(&vfd->lock);
|
|
+ idr_remove(&vi->vfds, vfd->id);
|
|
+ mutex_unlock(&vi->vfds_lock);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Only used to free a vfd that is not referenced any place else and contains
|
|
+ * no queed virtio buffers. This must not be called while vfd is included in a
|
|
+ * vi->vfd.
|
|
+ */
|
|
+static void virtwl_vfd_free(struct virtwl_vfd *vfd)
|
|
+{
|
|
+ kfree(vfd);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Thread safe and also removes vfd from vi as well as any queued virtio buffers
|
|
+ */
|
|
+static void virtwl_vfd_remove(struct virtwl_vfd *vfd)
|
|
+{
|
|
+ struct virtwl_info *vi = vfd->vi;
|
|
+ struct virtqueue *vq = vi->vqs[VIRTWL_VQ_IN];
|
|
+ struct mutex *vq_lock = &vi->vq_locks[VIRTWL_VQ_IN];
|
|
+ struct virtwl_vfd_qentry *qentry, *next;
|
|
+ virtwl_vfd_lock_unlink(vfd);
|
|
+
|
|
+ mutex_lock(vq_lock);
|
|
+ list_for_each_entry_safe(next, qentry, &vfd->in_queue, list) {
|
|
+ vq_return_inbuf_locked(vq, qentry->hdr);
|
|
+ list_del(&qentry->list);
|
|
+ kfree(qentry);
|
|
+ }
|
|
+ mutex_unlock(vq_lock);
|
|
+
|
|
+ virtwl_vfd_free(vfd);
|
|
+}
|
|
+
|
|
+static void vfd_qentry_free_if_empty(struct virtwl_vfd *vfd,
|
|
+ struct virtwl_vfd_qentry *qentry)
|
|
+{
|
|
+ struct virtwl_info *vi = vfd->vi;
|
|
+ struct virtqueue *vq = vi->vqs[VIRTWL_VQ_IN];
|
|
+ struct mutex *vq_lock = &vi->vq_locks[VIRTWL_VQ_IN];
|
|
+
|
|
+ if (qentry->hdr->type == VIRTIO_WL_CMD_VFD_RECV) {
|
|
+ struct virtio_wl_ctrl_vfd_recv *recv =
|
|
+ (struct virtio_wl_ctrl_vfd_recv *)qentry->hdr;
|
|
+ ssize_t data_len =
|
|
+ (ssize_t)qentry->len - (ssize_t)sizeof(*recv) -
|
|
+ (ssize_t)recv->vfd_count * (ssize_t)sizeof(__le32);
|
|
+
|
|
+ if (qentry->vfd_offset < recv->vfd_count)
|
|
+ return;
|
|
+
|
|
+ if ((s64)qentry->data_offset < data_len)
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ mutex_lock(vq_lock);
|
|
+ vq_return_inbuf_locked(vq, qentry->hdr);
|
|
+ mutex_unlock(vq_lock);
|
|
+ list_del(&qentry->list);
|
|
+ kfree(qentry);
|
|
+ virtqueue_kick(vq);
|
|
+}
|
|
+
|
|
+static ssize_t vfd_out_locked(struct virtwl_vfd *vfd, char __user *buffer,
|
|
+ size_t len)
|
|
+{
|
|
+ struct virtwl_vfd_qentry *qentry, *next;
|
|
+ ssize_t read_count = 0;
|
|
+
|
|
+ list_for_each_entry_safe(qentry, next, &vfd->in_queue, list) {
|
|
+ struct virtio_wl_ctrl_vfd_recv *recv =
|
|
+ (struct virtio_wl_ctrl_vfd_recv *)qentry->hdr;
|
|
+ size_t recv_offset = sizeof(*recv) + recv->vfd_count *
|
|
+ sizeof(__le32) + qentry->data_offset;
|
|
+ u8 *buf = (u8 *)recv + recv_offset;
|
|
+ ssize_t to_read = (ssize_t)qentry->len - (ssize_t)recv_offset;
|
|
+ if (read_count >= len)
|
|
+ break;
|
|
+ if (to_read <= 0)
|
|
+ continue;
|
|
+ if (qentry->hdr->type != VIRTIO_WL_CMD_VFD_RECV)
|
|
+ continue;
|
|
+
|
|
+ if ((to_read + read_count) > len)
|
|
+ to_read = len - read_count;
|
|
+
|
|
+ if (copy_to_user(buffer + read_count, buf, to_read)) {
|
|
+ /* return error unless we have some data to return */
|
|
+ if (read_count == 0)
|
|
+ read_count = -EFAULT;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ read_count += to_read;
|
|
+
|
|
+ qentry->data_offset += to_read;
|
|
+ vfd_qentry_free_if_empty(vfd, qentry);
|
|
+ }
|
|
+
|
|
+ return read_count;
|
|
+}
|
|
+
|
|
+static size_t vfd_out_vfds_locked(struct virtwl_vfd *vfd,
|
|
+ struct virtwl_vfd **vfds, size_t count)
|
|
+{
|
|
+ struct virtwl_info *vi = vfd->vi;
|
|
+ struct virtwl_vfd_qentry *qentry, *next;
|
|
+ size_t i;
|
|
+ size_t read_count = 0;
|
|
+
|
|
+ list_for_each_entry_safe(qentry, next, &vfd->in_queue, list) {
|
|
+ struct virtio_wl_ctrl_vfd_recv *recv =
|
|
+ (struct virtio_wl_ctrl_vfd_recv *)qentry->hdr;
|
|
+ size_t vfd_offset = sizeof(*recv) + qentry->vfd_offset *
|
|
+ sizeof(__le32);
|
|
+ __le32 *vfds_le = (__le32 *)((void *)recv + vfd_offset);
|
|
+ ssize_t vfds_to_read = recv->vfd_count - qentry->vfd_offset;
|
|
+ if (read_count >= count)
|
|
+ break;
|
|
+ if (vfds_to_read <= 0)
|
|
+ continue;
|
|
+ if (qentry->hdr->type != VIRTIO_WL_CMD_VFD_RECV)
|
|
+ continue;
|
|
+
|
|
+ if ((vfds_to_read + read_count) > count)
|
|
+ vfds_to_read = count - read_count;
|
|
+
|
|
+ for (i = 0; i < vfds_to_read; i++) {
|
|
+ uint32_t vfd_id = le32_to_cpu(vfds_le[i]);
|
|
+ /*
|
|
+ This is an inversion of the typical locking order
|
|
+ (vi->vfds_lock before vfd->lock). The reason this is
|
|
+ safe from deadlocks is because the lock held as a
|
|
+ precondition of this function call is always for a
|
|
+ different vfd than the one received on this vfd's queue.
|
|
+ */
|
|
+ mutex_lock(&vi->vfds_lock);
|
|
+ vfds[read_count] = idr_find(&vi->vfds, vfd_id);
|
|
+ mutex_unlock(&vi->vfds_lock);
|
|
+ if (vfds[read_count]) {
|
|
+ read_count++;
|
|
+ } else {
|
|
+ printk("virtwl: received a vfd with unrecognized id: %u\n",
|
|
+ vfd_id);
|
|
+ }
|
|
+ qentry->vfd_offset++;
|
|
+ }
|
|
+
|
|
+ vfd_qentry_free_if_empty(vfd, qentry);
|
|
+ }
|
|
+
|
|
+ return read_count;
|
|
+}
|
|
+
|
|
+/* this can only be called if the caller has unique ownership of the vfd */
|
|
+static int do_vfd_close(struct virtwl_vfd *vfd)
|
|
+{
|
|
+ struct virtio_wl_ctrl_vfd *ctrl_close;
|
|
+ struct virtwl_info *vi = vfd->vi;
|
|
+ struct completion finish_completion;
|
|
+ struct scatterlist out_sg;
|
|
+ struct scatterlist in_sg;
|
|
+ int ret = 0;
|
|
+
|
|
+ ctrl_close = kzalloc(sizeof(*ctrl_close), GFP_KERNEL);
|
|
+ if (!ctrl_close)
|
|
+ return -ENOMEM;
|
|
+
|
|
+ ctrl_close->hdr.type = VIRTIO_WL_CMD_VFD_CLOSE;
|
|
+ ctrl_close->vfd_id = vfd->id;
|
|
+
|
|
+ sg_init_one(&in_sg, &ctrl_close->hdr, sizeof(struct virtio_wl_ctrl_vfd));
|
|
+ sg_init_one(&out_sg, &ctrl_close->hdr, sizeof(struct virtio_wl_ctrl_hdr));
|
|
+
|
|
+ init_completion(&finish_completion);
|
|
+ ret = vq_queue_out(vi, &out_sg, &in_sg, &finish_completion,
|
|
+ false /* block */);
|
|
+ if (ret) {
|
|
+ printk("virtwl: failed to queue close vfd id %u: %d\n", vfd->id,
|
|
+ ret);
|
|
+ goto free_ctrl_close;
|
|
+ }
|
|
+
|
|
+ wait_for_completion(&finish_completion);
|
|
+ virtwl_vfd_remove(vfd);
|
|
+
|
|
+free_ctrl_close:
|
|
+ kfree(ctrl_close);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static ssize_t virtwl_vfd_recv(struct file *filp, char __user *buffer,
|
|
+ size_t len, struct virtwl_vfd **vfds,
|
|
+ size_t *vfd_count)
|
|
+{
|
|
+ struct virtwl_vfd *vfd = filp->private_data;
|
|
+ ssize_t read_count = 0;
|
|
+ size_t vfd_read_count = 0;
|
|
+
|
|
+ mutex_lock(&vfd->lock);
|
|
+
|
|
+ while (read_count == 0 && vfd_read_count == 0) {
|
|
+ while (list_empty(&vfd->in_queue)) {
|
|
+ mutex_unlock(&vfd->lock);
|
|
+ if (filp->f_flags & O_NONBLOCK)
|
|
+ return -EAGAIN;
|
|
+
|
|
+ if (wait_event_interruptible(vfd->in_waitq,
|
|
+ !list_empty(&vfd->in_queue)))
|
|
+ return -ERESTARTSYS;
|
|
+
|
|
+ mutex_lock(&vfd->lock);
|
|
+ }
|
|
+
|
|
+ read_count = vfd_out_locked(vfd, buffer, len);
|
|
+ if (read_count < 0)
|
|
+ goto out_unlock;
|
|
+ if (vfds && vfd_count && *vfd_count)
|
|
+ vfd_read_count = vfd_out_vfds_locked(vfd, vfds,
|
|
+ *vfd_count);
|
|
+ }
|
|
+
|
|
+ *vfd_count = vfd_read_count;
|
|
+
|
|
+out_unlock:
|
|
+ mutex_unlock(&vfd->lock);
|
|
+ return read_count;
|
|
+}
|
|
+
|
|
+static int virtwl_vfd_mmap(struct file *filp, struct vm_area_struct *vma)
|
|
+{
|
|
+ struct virtwl_vfd *vfd = filp->private_data;
|
|
+ unsigned long vm_size = vma->vm_end - vma->vm_start;
|
|
+ int ret = 0;
|
|
+
|
|
+ mutex_lock(&vfd->lock);
|
|
+
|
|
+ if (!(vfd->flags & VIRTIO_WL_VFD_MAP)) {
|
|
+ ret = -EACCES;
|
|
+ goto out_unlock;
|
|
+ }
|
|
+
|
|
+ if ((vma->vm_flags & VM_WRITE) && !(vfd->flags & VIRTIO_WL_VFD_WRITE)) {
|
|
+ ret = -EACCES;
|
|
+ goto out_unlock;
|
|
+ }
|
|
+
|
|
+ if (vm_size + (vma->vm_pgoff << PAGE_SHIFT) > PAGE_ALIGN(vfd->size)) {
|
|
+ ret = -EINVAL;
|
|
+ goto out_unlock;
|
|
+ }
|
|
+
|
|
+ ret = io_remap_pfn_range(vma, vma->vm_start, vfd->pfn, vm_size,
|
|
+ vma->vm_page_prot);
|
|
+ if (ret)
|
|
+ goto out_unlock;
|
|
+
|
|
+ vma->vm_flags |= VM_PFNMAP | VM_IO | VM_DONTEXPAND | VM_DONTDUMP;
|
|
+
|
|
+out_unlock:
|
|
+ mutex_unlock(&vfd->lock);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static unsigned int virtwl_vfd_poll(struct file *filp,
|
|
+ struct poll_table_struct *wait)
|
|
+{
|
|
+ struct virtwl_vfd *vfd = filp->private_data;
|
|
+ struct virtwl_info *vi = vfd->vi;
|
|
+ unsigned int mask = 0;
|
|
+
|
|
+ mutex_lock(&vi->vq_locks[VIRTWL_VQ_OUT]);
|
|
+ poll_wait(filp, &vi->out_waitq, wait);
|
|
+ if (vi->vqs[VIRTWL_VQ_OUT]->num_free)
|
|
+ mask |= POLLOUT | POLLWRNORM;
|
|
+ mutex_unlock(&vi->vq_locks[VIRTWL_VQ_OUT]);
|
|
+
|
|
+ mutex_lock(&vfd->lock);
|
|
+ poll_wait(filp, &vfd->in_waitq, wait);
|
|
+ if (!list_empty(&vfd->in_queue))
|
|
+ mask |= POLLIN | POLLRDNORM;
|
|
+ mutex_unlock(&vfd->lock);
|
|
+
|
|
+ return mask;
|
|
+}
|
|
+
|
|
+static int virtwl_vfd_release(struct inode *inodep, struct file *filp)
|
|
+{
|
|
+ struct virtwl_vfd *vfd = filp->private_data;
|
|
+ uint32_t vfd_id = vfd->id;
|
|
+
|
|
+ /*
|
|
+ * if release is called, filp must be out of references and we have the
|
|
+ * last reference
|
|
+ */
|
|
+ int ret = do_vfd_close(vfd);
|
|
+ if (ret)
|
|
+ printk("virtwl: failed to release vfd id %u: %d\n", vfd_id,
|
|
+ ret);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int virtwl_open(struct inode *inodep, struct file *filp)
|
|
+{
|
|
+ struct virtwl_info *vi = container_of(inodep->i_cdev,
|
|
+ struct virtwl_info, cdev);
|
|
+
|
|
+ filp->private_data = vi;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int do_send(struct virtwl_vfd *vfd, const char __user *buffer, u32 len,
|
|
+ int *vfd_fds, bool nonblock)
|
|
+{
|
|
+ struct virtwl_info *vi = vfd->vi;
|
|
+ struct fd vfd_files[VIRTWL_SEND_MAX_ALLOCS] = { { 0 } };
|
|
+ struct virtwl_vfd *vfds[VIRTWL_SEND_MAX_ALLOCS] = { 0 };
|
|
+ size_t vfd_count = 0;
|
|
+ size_t post_send_size;
|
|
+ struct virtio_wl_ctrl_vfd_send *ctrl_send;
|
|
+ __le32 *vfd_ids;
|
|
+ u8 *out_buffer;
|
|
+ unsigned long remaining;
|
|
+ struct completion finish_completion;
|
|
+ struct scatterlist out_sg;
|
|
+ struct scatterlist in_sg;
|
|
+ int ret;
|
|
+ int i;
|
|
+
|
|
+ if (vfd_fds) {
|
|
+ for (i = 0; i < VIRTWL_SEND_MAX_ALLOCS; i++) {
|
|
+ struct fd vfd_file;
|
|
+ int fd = vfd_fds[i];
|
|
+ if (fd < 0)
|
|
+ break;
|
|
+
|
|
+ vfd_file = fdget(vfd_fds[i]);
|
|
+ if (!vfd_file.file) {
|
|
+ ret = -EBADFD;
|
|
+ goto put_files;
|
|
+ }
|
|
+ vfd_files[i] = vfd_file;
|
|
+
|
|
+ if (vfd_file.file->f_op != &virtwl_vfd_fops) {
|
|
+ ret = -EINVAL;
|
|
+ goto put_files;
|
|
+ }
|
|
+
|
|
+ vfds[i] = vfd_file.file->private_data;
|
|
+ if (!vfds[i] || !vfds[i]->id) {
|
|
+ ret = -EINVAL;
|
|
+ goto put_files;
|
|
+ }
|
|
+
|
|
+ vfd_count++;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ post_send_size = vfd_count * sizeof(__le32) + len;
|
|
+ ctrl_send = kzalloc(sizeof(*ctrl_send) + post_send_size, GFP_KERNEL);
|
|
+ if (!ctrl_send) {
|
|
+ ret = -ENOMEM;
|
|
+ goto put_files;
|
|
+ }
|
|
+
|
|
+ vfd_ids = (__le32 *)((u8*)ctrl_send + sizeof(*ctrl_send));
|
|
+ out_buffer = (u8*)vfd_ids + vfd_count * sizeof(__le32);
|
|
+
|
|
+ ctrl_send->hdr.type = VIRTIO_WL_CMD_VFD_SEND;
|
|
+ ctrl_send->vfd_id = vfd->id;
|
|
+ ctrl_send->vfd_count = vfd_count;
|
|
+ for (i = 0; i < vfd_count; i++) {
|
|
+ vfd_ids[i] = cpu_to_le32(vfds[i]->id);
|
|
+ }
|
|
+
|
|
+ remaining = copy_from_user(out_buffer, buffer, len);
|
|
+ if (remaining)
|
|
+ goto free_ctrl_send;
|
|
+
|
|
+ init_completion(&finish_completion);
|
|
+ sg_init_one(&out_sg, ctrl_send, sizeof(*ctrl_send) + post_send_size);
|
|
+ sg_init_one(&in_sg, ctrl_send, sizeof(struct virtio_wl_ctrl_hdr));
|
|
+
|
|
+ ret = vq_queue_out(vi, &out_sg, &in_sg, &finish_completion, nonblock);
|
|
+ if (ret)
|
|
+ goto free_ctrl_send;
|
|
+
|
|
+ wait_for_completion(&finish_completion);
|
|
+
|
|
+ ret = virtwl_resp_err(ctrl_send->hdr.type);
|
|
+
|
|
+free_ctrl_send:
|
|
+ kfree(ctrl_send);
|
|
+put_files:
|
|
+ for (i = 0; i < VIRTWL_SEND_MAX_ALLOCS; i++) {
|
|
+ if (!vfd_files[i].file)
|
|
+ continue;
|
|
+ fdput(vfd_files[i]);
|
|
+ }
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static struct virtwl_vfd *do_new(struct virtwl_info *vi, uint32_t type,
|
|
+ uint32_t size, bool nonblock)
|
|
+{
|
|
+ struct virtio_wl_ctrl_vfd_new *ctrl_new;
|
|
+ struct virtwl_vfd *vfd;
|
|
+ struct completion finish_completion;
|
|
+ struct scatterlist out_sg;
|
|
+ struct scatterlist in_sg;
|
|
+ int ret = 0;
|
|
+
|
|
+ if (type != VIRTWL_IOCTL_NEW_CTX && type != VIRTWL_IOCTL_NEW_ALLOC)
|
|
+ return ERR_PTR(-EINVAL);
|
|
+
|
|
+ ctrl_new = kzalloc(sizeof(*ctrl_new), GFP_KERNEL);
|
|
+ if (!ctrl_new)
|
|
+ return ERR_PTR(-ENOMEM);
|
|
+
|
|
+ vfd = virtwl_vfd_alloc(vi);
|
|
+ if (!vfd) {
|
|
+ ret = -ENOMEM;
|
|
+ goto free_ctrl_new;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Take the lock before adding it to the vfds list where others might
|
|
+ * reference it.
|
|
+ */
|
|
+ mutex_lock(&vfd->lock);
|
|
+
|
|
+ mutex_lock(&vi->vfds_lock);
|
|
+ ret = idr_alloc(&vi->vfds, vfd, 1, VIRTWL_MAX_ALLOC, GFP_KERNEL);
|
|
+ mutex_unlock(&vi->vfds_lock);
|
|
+ if (ret <= 0)
|
|
+ goto free_vfd;
|
|
+
|
|
+ vfd->id = ret;
|
|
+ ret = 0;
|
|
+
|
|
+ ctrl_new->vfd_id = vfd->id;
|
|
+ switch (type) {
|
|
+ case VIRTWL_IOCTL_NEW_CTX:
|
|
+ ctrl_new->hdr.type = VIRTIO_WL_CMD_VFD_NEW_CTX;
|
|
+ ctrl_new->flags = VIRTIO_WL_VFD_CONTROL;
|
|
+ ctrl_new->size = 0;
|
|
+ break;
|
|
+ case VIRTWL_IOCTL_NEW_ALLOC:
|
|
+ ctrl_new->hdr.type = VIRTIO_WL_CMD_VFD_NEW;
|
|
+ ctrl_new->flags = VIRTIO_WL_VFD_WRITE | VIRTIO_WL_VFD_MAP;
|
|
+ ctrl_new->size = size;
|
|
+ break;
|
|
+ default:
|
|
+ ret = -EINVAL;
|
|
+ goto remove_vfd;
|
|
+ }
|
|
+
|
|
+ init_completion(&finish_completion);
|
|
+ sg_init_one(&out_sg, ctrl_new, sizeof(*ctrl_new));
|
|
+ sg_init_one(&in_sg, ctrl_new, sizeof(*ctrl_new));
|
|
+
|
|
+ ret = vq_queue_out(vi, &out_sg, &in_sg, &finish_completion, nonblock);
|
|
+ if (ret)
|
|
+ goto remove_vfd;
|
|
+
|
|
+ wait_for_completion(&finish_completion);
|
|
+
|
|
+ ret = virtwl_resp_err(ctrl_new->hdr.type);
|
|
+ if (ret)
|
|
+ goto remove_vfd;
|
|
+
|
|
+ vfd->size = ctrl_new->size;
|
|
+ vfd->pfn = ctrl_new->pfn;
|
|
+ vfd->flags = ctrl_new->flags;
|
|
+
|
|
+ mutex_unlock(&vfd->lock);
|
|
+
|
|
+ kfree(ctrl_new);
|
|
+ return vfd;
|
|
+
|
|
+remove_vfd:
|
|
+ /* unlock the vfd to avoid deadlock when unlinking it */
|
|
+ mutex_unlock(&vfd->lock);
|
|
+ virtwl_vfd_lock_unlink(vfd);
|
|
+free_vfd:
|
|
+ virtwl_vfd_free(vfd);
|
|
+free_ctrl_new:
|
|
+ kfree(ctrl_new);
|
|
+ return ERR_PTR(ret);
|
|
+}
|
|
+
|
|
+static long virtwl_ioctl_send(struct file *filp, unsigned long arg)
|
|
+{
|
|
+ struct virtwl_vfd *vfd = filp->private_data;
|
|
+ struct virtwl_ioctl_send ioctl_send;
|
|
+ void __user *user_data = (void __user *)arg +
|
|
+ sizeof(struct virtwl_ioctl_send);
|
|
+ int ret;
|
|
+
|
|
+ ret = copy_from_user(&ioctl_send, (void __user *)arg,
|
|
+ sizeof(struct virtwl_ioctl_send));
|
|
+ if (ret)
|
|
+ return -EFAULT;
|
|
+
|
|
+ /* Early check for user error; do_send still uses copy_from_user. */
|
|
+ ret = !access_ok(VERIFY_READ, user_data, ioctl_send.len);
|
|
+ if (ret)
|
|
+ return -EFAULT;
|
|
+
|
|
+ return do_send(vfd, user_data, ioctl_send.len, ioctl_send.fds,
|
|
+ filp->f_flags & O_NONBLOCK);
|
|
+}
|
|
+
|
|
+static long virtwl_ioctl_recv(struct file *filp, unsigned long arg)
|
|
+{
|
|
+ struct virtwl_ioctl_recv ioctl_recv;
|
|
+ void __user *user_data = (void __user *)arg +
|
|
+ sizeof(struct virtwl_ioctl_recv);
|
|
+ int __user *user_fds = (int __user *)arg;
|
|
+ size_t vfd_count = VIRTWL_SEND_MAX_ALLOCS;
|
|
+ struct virtwl_vfd *vfds[VIRTWL_SEND_MAX_ALLOCS] = { 0 };
|
|
+ int fds[VIRTWL_SEND_MAX_ALLOCS];
|
|
+ size_t i;
|
|
+ int ret = 0;
|
|
+
|
|
+
|
|
+ for (i = 0; i < VIRTWL_SEND_MAX_ALLOCS; i++)
|
|
+ fds[i] = -1;
|
|
+
|
|
+ ret = copy_from_user(&ioctl_recv, (void __user *)arg,
|
|
+ sizeof(struct virtwl_ioctl_recv));
|
|
+ if (ret)
|
|
+ return -EFAULT;
|
|
+
|
|
+ /* Early check for user error. */
|
|
+ ret = !access_ok(VERIFY_WRITE, user_data, ioctl_recv.len);
|
|
+ if (ret)
|
|
+ return -EFAULT;
|
|
+
|
|
+ ret = virtwl_vfd_recv(filp, user_data, ioctl_recv.len, vfds,
|
|
+ &vfd_count);
|
|
+ if (ret < 0)
|
|
+ return ret;
|
|
+
|
|
+ ret = copy_to_user(&((struct virtwl_ioctl_recv __user *)arg)->len, &ret,
|
|
+ sizeof(ioctl_recv.len));
|
|
+ if (ret) {
|
|
+ ret = -EFAULT;
|
|
+ goto free_vfds;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < vfd_count; i++) {
|
|
+ ret = anon_inode_getfd("[virtwl_vfd]", &virtwl_vfd_fops,
|
|
+ vfds[i], O_CLOEXEC | O_RDWR);
|
|
+ if (ret < 0) {
|
|
+ do_vfd_close(vfds[i]);
|
|
+ goto free_vfds;
|
|
+ }
|
|
+ vfds[i] = NULL;
|
|
+ fds[i] = ret;
|
|
+ }
|
|
+
|
|
+ ret = copy_to_user(user_fds, fds, sizeof(int) * VIRTWL_SEND_MAX_ALLOCS);
|
|
+ if (ret) {
|
|
+ ret = -EFAULT;
|
|
+ goto free_vfds;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+
|
|
+free_vfds:
|
|
+ for (i = 0; i < vfd_count; i++) {
|
|
+ if (vfds[i])
|
|
+ do_vfd_close(vfds[i]);
|
|
+ if (fds[i] >= 0)
|
|
+ __close_fd(current->files, fds[i]);
|
|
+ }
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static long virtwl_vfd_ioctl(struct file *filp, unsigned int cmd,
|
|
+ unsigned long arg)
|
|
+{
|
|
+ switch (cmd) {
|
|
+ case VIRTWL_IOCTL_SEND:
|
|
+ return virtwl_ioctl_send(filp, arg);
|
|
+ case VIRTWL_IOCTL_RECV:
|
|
+ return virtwl_ioctl_recv(filp, arg);
|
|
+ default:
|
|
+ return -ENOTTY;
|
|
+ }
|
|
+}
|
|
+
|
|
+static long virtwl_ioctl_new(struct file *filp, unsigned long arg)
|
|
+{
|
|
+ struct virtwl_info *vi = filp->private_data;
|
|
+ struct virtwl_vfd *vfd;
|
|
+ struct virtwl_ioctl_new ioctl_new;
|
|
+ int ret;
|
|
+
|
|
+ ret = copy_from_user(&ioctl_new, (void __user *)arg,
|
|
+ sizeof(struct virtwl_ioctl_new));
|
|
+ if (ret)
|
|
+ return -EFAULT;
|
|
+
|
|
+ ioctl_new.size = PAGE_ALIGN(ioctl_new.size);
|
|
+
|
|
+ vfd = do_new(vi, ioctl_new.type, ioctl_new.size,
|
|
+ filp->f_flags & O_NONBLOCK);
|
|
+ if (IS_ERR(vfd))
|
|
+ return PTR_ERR(vfd);
|
|
+
|
|
+ ret = anon_inode_getfd("[virtwl_vfd]", &virtwl_vfd_fops, vfd,
|
|
+ O_CLOEXEC | O_RDWR);
|
|
+ if (ret < 0) {
|
|
+ do_vfd_close(vfd);
|
|
+ return ret;
|
|
+ }
|
|
+
|
|
+ ioctl_new.fd = ret;
|
|
+ ret = copy_to_user((void __user *)arg, &ioctl_new,
|
|
+ sizeof(struct virtwl_ioctl_new));
|
|
+ if (ret) {
|
|
+ /* The release operation will handle freeing this alloc */
|
|
+ sys_close(ioctl_new.fd);
|
|
+ return -EFAULT;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static long virtwl_ioctl(struct file *filp, unsigned int cmd,
|
|
+ unsigned long arg)
|
|
+{
|
|
+ int err = 0;
|
|
+
|
|
+ if (_IOC_TYPE(cmd) != VIRTWL_IOCTL_BASE)
|
|
+ return -ENOTTY;
|
|
+ if (_IOC_NR(cmd) > VIRTWL_IOCTL_MAXNR)
|
|
+ return -ENOTTY;
|
|
+
|
|
+ if (_IOC_DIR(cmd) & _IOC_READ) {
|
|
+ err = !access_ok(VERIFY_WRITE, (void __user *)arg,
|
|
+ _IOC_SIZE(cmd));
|
|
+ } else if (_IOC_DIR(cmd) & _IOC_WRITE) {
|
|
+ err = !access_ok(VERIFY_READ, (void __user *)arg,
|
|
+ _IOC_SIZE(cmd));
|
|
+ }
|
|
+
|
|
+ if (err)
|
|
+ return -EFAULT;
|
|
+
|
|
+ if (filp->f_op == &virtwl_vfd_fops)
|
|
+ return virtwl_vfd_ioctl(filp, cmd, arg);
|
|
+
|
|
+ switch (cmd) {
|
|
+ case VIRTWL_IOCTL_NEW:
|
|
+ return virtwl_ioctl_new(filp, arg);
|
|
+ default:
|
|
+ return -ENOTTY;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int virtwl_release(struct inode *inodep, struct file *filp)
|
|
+{
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct file_operations virtwl_fops =
|
|
+{
|
|
+ .open = virtwl_open,
|
|
+ .unlocked_ioctl = virtwl_ioctl,
|
|
+ .release = virtwl_release,
|
|
+};
|
|
+
|
|
+static struct file_operations virtwl_vfd_fops =
|
|
+{
|
|
+ .mmap = virtwl_vfd_mmap,
|
|
+ .poll = virtwl_vfd_poll,
|
|
+ .unlocked_ioctl = virtwl_ioctl,
|
|
+ .release = virtwl_vfd_release,
|
|
+};
|
|
+
|
|
+static int probe_common(struct virtio_device *vdev)
|
|
+{
|
|
+ int i;
|
|
+ int ret;
|
|
+ struct virtwl_info *vi = NULL;
|
|
+ vq_callback_t *vq_callbacks[] = { vq_in_cb, vq_out_cb };
|
|
+ const char *vq_names[] = { "in", "out" };
|
|
+
|
|
+ vi = kzalloc(sizeof(struct virtwl_info), GFP_KERNEL);
|
|
+ if (!vi) {
|
|
+ printk("virtwl: failed to alloc virtwl_info struct\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ vdev->priv = vi;
|
|
+
|
|
+ ret = alloc_chrdev_region(&vi->dev_num, 0, 1, "wl");
|
|
+ if (ret) {
|
|
+ ret = -ENOMEM;
|
|
+ printk("virtwl: failed to allocate wl chrdev region: %d\n",
|
|
+ ret);
|
|
+ goto free_vi;
|
|
+ }
|
|
+
|
|
+ vi->class = class_create(THIS_MODULE, "wl");
|
|
+ if (IS_ERR(vi->class)) {
|
|
+ ret = PTR_ERR(vi->class);
|
|
+ printk("virtwl: failed to create wl class: %d\n", ret);
|
|
+ goto unregister_region;
|
|
+
|
|
+ }
|
|
+
|
|
+ vi->dev = device_create(vi->class, NULL, vi->dev_num, vi, "wl%d", 0);
|
|
+ if (IS_ERR(vi->dev)) {
|
|
+ ret = PTR_ERR(vi->dev);
|
|
+ printk("virtwl: failed to create wl0 device: %d\n", ret);
|
|
+ goto destroy_class;
|
|
+ }
|
|
+
|
|
+ cdev_init(&vi->cdev, &virtwl_fops);
|
|
+ ret = cdev_add(&vi->cdev, vi->dev_num, 1);
|
|
+ if (ret) {
|
|
+ printk("virtwl: failed to add virtio wayland character device to system: %d\n",
|
|
+ ret);
|
|
+ goto destroy_device;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < VIRTWL_QUEUE_COUNT; i++)
|
|
+ mutex_init(&vi->vq_locks[i]);
|
|
+
|
|
+ ret = vdev->config->find_vqs(vdev, VIRTWL_QUEUE_COUNT, vi->vqs,
|
|
+ vq_callbacks, vq_names);
|
|
+ if (ret) {
|
|
+ printk("virtwl: failed to find virtio wayland queues: %d\n",
|
|
+ ret);
|
|
+ goto del_cdev;
|
|
+ }
|
|
+
|
|
+ INIT_WORK(&vi->in_vq_work, vq_in_work_handler);
|
|
+ INIT_WORK(&vi->out_vq_work, vq_out_work_handler);
|
|
+ init_waitqueue_head(&vi->out_waitq);
|
|
+
|
|
+ mutex_init(&vi->vfds_lock);
|
|
+ idr_init(&vi->vfds);
|
|
+
|
|
+ /* lock is unneeded as we have unique ownership */
|
|
+ ret = vq_fill_locked(vi->vqs[VIRTWL_VQ_IN]);
|
|
+ if (ret) {
|
|
+ printk("virtwl: failed to fill in virtqueue: %d", ret);
|
|
+ goto del_cdev;
|
|
+ }
|
|
+
|
|
+ virtio_device_ready(vdev);
|
|
+ virtqueue_kick(vi->vqs[VIRTWL_VQ_IN]);
|
|
+
|
|
+
|
|
+ return 0;
|
|
+
|
|
+del_cdev:
|
|
+ cdev_del(&vi->cdev);
|
|
+destroy_device:
|
|
+ put_device(vi->dev);
|
|
+destroy_class:
|
|
+ class_destroy(vi->class);
|
|
+unregister_region:
|
|
+ unregister_chrdev_region(vi->dev_num, 0);
|
|
+free_vi:
|
|
+ kfree(vi);
|
|
+ return ret;
|
|
+}
|
|
+
|
|
+static void remove_common(struct virtio_device *vdev)
|
|
+{
|
|
+ struct virtwl_info *vi = vdev->priv;
|
|
+
|
|
+ cdev_del(&vi->cdev);
|
|
+ put_device(vi->dev);
|
|
+ class_destroy(vi->class);
|
|
+ unregister_chrdev_region(vi->dev_num, 0);
|
|
+ kfree(vi);
|
|
+}
|
|
+
|
|
+static int virtwl_probe(struct virtio_device *vdev)
|
|
+{
|
|
+ return probe_common(vdev);
|
|
+}
|
|
+
|
|
+static void virtwl_remove(struct virtio_device *vdev)
|
|
+{
|
|
+ remove_common(vdev);
|
|
+}
|
|
+
|
|
+static void virtwl_scan(struct virtio_device *vdev)
|
|
+{
|
|
+}
|
|
+
|
|
+
|
|
+static struct virtio_device_id id_table[] = {
|
|
+ { VIRTIO_ID_WL, VIRTIO_DEV_ANY_ID },
|
|
+ { 0 },
|
|
+};
|
|
+
|
|
+static struct virtio_driver virtio_wl_driver = {
|
|
+ .driver.name = KBUILD_MODNAME,
|
|
+ .driver.owner = THIS_MODULE,
|
|
+ .id_table = id_table,
|
|
+ .probe = virtwl_probe,
|
|
+ .remove = virtwl_remove,
|
|
+ .scan = virtwl_scan,
|
|
+};
|
|
+
|
|
+module_virtio_driver(virtio_wl_driver);
|
|
+MODULE_DEVICE_TABLE(virtio, id_table);
|
|
+MODULE_DESCRIPTION("Virtio wayland driver");
|
|
+MODULE_LICENSE("GPL");
|
|
diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild
|
|
index efa2a9c..6d1095b 100644
|
|
--- a/include/uapi/linux/Kbuild
|
|
+++ b/include/uapi/linux/Kbuild
|
|
@@ -449,6 +449,8 @@
|
|
header-y += virtio_scsi.h
|
|
header-y += virtio_types.h
|
|
header-y += virtio_vsock.h
|
|
+header-y += virtio_wl.h
|
|
+header-y += virtwl.h
|
|
header-y += vm_sockets.h
|
|
header-y += vt.h
|
|
header-y += vtpm_proxy.h
|
|
diff --git a/include/uapi/linux/virtio_ids.h b/include/uapi/linux/virtio_ids.h
|
|
index 3228d58..68a2a8d 100644
|
|
--- a/include/uapi/linux/virtio_ids.h
|
|
+++ b/include/uapi/linux/virtio_ids.h
|
|
@@ -42,5 +42,6 @@
|
|
#define VIRTIO_ID_GPU 16 /* virtio GPU */
|
|
#define VIRTIO_ID_INPUT 18 /* virtio input */
|
|
#define VIRTIO_ID_VSOCK 19 /* virtio vsock transport */
|
|
+#define VIRTIO_ID_WL 30 /* virtio wayland */
|
|
|
|
#endif /* _LINUX_VIRTIO_IDS_H */
|
|
diff --git a/include/uapi/linux/virtio_wl.h b/include/uapi/linux/virtio_wl.h
|
|
new file mode 100644
|
|
index 0000000..b3cdadc
|
|
--- /dev/null
|
|
+++ b/include/uapi/linux/virtio_wl.h
|
|
@@ -0,0 +1,86 @@
|
|
+#ifndef _LINUX_VIRTIO_WL_H
|
|
+#define _LINUX_VIRTIO_WL_H
|
|
+/* This header is BSD licensed so anyone can use the definitions to implement
|
|
+ * compatible drivers/servers. */
|
|
+#include <linux/virtio_ids.h>
|
|
+#include <linux/virtio_config.h>
|
|
+#include <linux/virtwl.h>
|
|
+
|
|
+
|
|
+#define VIRTWL_IN_BUFFER_SIZE 4096
|
|
+#define VIRTWL_OUT_BUFFER_SIZE 4096
|
|
+#define VIRTWL_VQ_IN 0
|
|
+#define VIRTWL_VQ_OUT 1
|
|
+#define VIRTWL_QUEUE_COUNT 2
|
|
+#define VIRTWL_MAX_ALLOC 0x800
|
|
+#define VIRTWL_PFN_SHIFT 12
|
|
+
|
|
+struct virtio_wl_config {
|
|
+
|
|
+};
|
|
+
|
|
+/*
|
|
+ * The structure of each of these is virtio_wl_ctrl_hdr or one of its subclasses
|
|
+ * where noted. */
|
|
+enum virtio_wl_ctrl_type {
|
|
+ VIRTIO_WL_CMD_VFD_NEW = 0x100, /* virtio_wl_ctrl_vfd_new */
|
|
+ VIRTIO_WL_CMD_VFD_CLOSE, /* virtio_wl_ctrl_vfd */
|
|
+ VIRTIO_WL_CMD_VFD_SEND, /* virtio_wl_ctrl_vfd_send + data */
|
|
+ VIRTIO_WL_CMD_VFD_RECV, /* virtio_wl_ctrl_vfd_recv + data */
|
|
+ VIRTIO_WL_CMD_VFD_NEW_CTX, /* virtio_wl_ctrl_vfd */
|
|
+
|
|
+ VIRTIO_WL_RESP_OK = 0x1000,
|
|
+ VIRTIO_WL_RESP_VFD_NEW = 0x1001, /* virtio_wl_ctrl_vfd_new */
|
|
+
|
|
+ VIRTIO_WL_RESP_ERR = 0x1100,
|
|
+ VIRTIO_WL_RESP_OUT_OF_MEMORY,
|
|
+ VIRTIO_WL_RESP_INVALID_ID,
|
|
+ VIRTIO_WL_RESP_INVALID_TYPE,
|
|
+};
|
|
+
|
|
+struct virtio_wl_ctrl_hdr {
|
|
+ __le32 type; /* one of virtio_wl_ctrl_type */
|
|
+ __le32 flags; /* always 0 */
|
|
+};
|
|
+
|
|
+enum virtio_wl_vfd_flags {
|
|
+ VIRTIO_WL_VFD_WRITE = 0x1, /* indicates if mapped area is writable */
|
|
+ VIRTIO_WL_VFD_MAP = 0x2, /* indicates a fixed size and mapping into a pfn range */
|
|
+ VIRTIO_WL_VFD_CONTROL = 0x4, /* indicates if send/recv can transmit VFDs */
|
|
+};
|
|
+
|
|
+struct virtio_wl_ctrl_vfd {
|
|
+ struct virtio_wl_ctrl_hdr hdr;
|
|
+ __le32 vfd_id;
|
|
+};
|
|
+
|
|
+/*
|
|
+ * If this command is sent to the guest, it indicates that the VFD has been
|
|
+ * created and the fields indicate the properties of the VFD being offered.
|
|
+ *
|
|
+ * If this command is sent to the host, it represents a request to create a VFD
|
|
+ * of the given properties. The pfn field is ignored by the host.
|
|
+ */
|
|
+struct virtio_wl_ctrl_vfd_new {
|
|
+ struct virtio_wl_ctrl_hdr hdr;
|
|
+ __le32 vfd_id; /* MSB indicates device allocated vfd */
|
|
+ __le32 flags; /* virtio_wl_vfd_flags */
|
|
+ __le64 pfn; /* first guest physical page frame number if VIRTIO_WL_VFD_MAP */
|
|
+ __le32 size; /* size in bytes if VIRTIO_WL_VFD_MAP */
|
|
+};
|
|
+
|
|
+struct virtio_wl_ctrl_vfd_send {
|
|
+ struct virtio_wl_ctrl_hdr hdr;
|
|
+ __le32 vfd_id;
|
|
+ __le32 vfd_count; /* struct is followed by this many IDs */
|
|
+ /* the remainder is raw data */
|
|
+};
|
|
+
|
|
+struct virtio_wl_ctrl_vfd_recv {
|
|
+ struct virtio_wl_ctrl_hdr hdr;
|
|
+ __le32 vfd_id;
|
|
+ __le32 vfd_count; /* struct is followed by this many IDs */
|
|
+ /* the remainder is raw data */
|
|
+};
|
|
+
|
|
+#endif /* _LINUX_VIRTIO_WL_H */
|
|
diff --git a/include/uapi/linux/virtwl.h b/include/uapi/linux/virtwl.h
|
|
new file mode 100644
|
|
index 0000000..7a43ce2
|
|
--- /dev/null
|
|
+++ b/include/uapi/linux/virtwl.h
|
|
@@ -0,0 +1,45 @@
|
|
+#ifndef _LINUX_VIRTWL_H
|
|
+#define _LINUX_VIRTWL_H
|
|
+
|
|
+#include <asm/ioctl.h>
|
|
+
|
|
+#define VIRTWL_SEND_MAX_ALLOCS 16
|
|
+
|
|
+#define VIRTWL_IOCTL_BASE 'w'
|
|
+#define VIRTWL_IO(nr) _IO(VIRTWL_IOCTL_BASE,nr)
|
|
+#define VIRTWL_IOR(nr,type) _IOR(VIRTWL_IOCTL_BASE,nr,type)
|
|
+#define VIRTWL_IOW(nr,type) _IOW(VIRTWL_IOCTL_BASE,nr,type)
|
|
+#define VIRTWL_IOWR(nr,type) _IOWR(VIRTWL_IOCTL_BASE,nr,type)
|
|
+
|
|
+enum virtwl_ioctl_new_type {
|
|
+ VIRTWL_IOCTL_NEW_CTX, // struct virtwl_ioctl_new
|
|
+ VIRTWL_IOCTL_NEW_ALLOC, // struct virtwl_ioctl_new_alloc
|
|
+};
|
|
+
|
|
+struct virtwl_ioctl_new {
|
|
+ uint32_t type; // always 0
|
|
+ int fd; // return fd
|
|
+ uint32_t flags; // always 0
|
|
+ size_t size; // only for VIRTWL_IOCTL_NEW_ALLOC
|
|
+};
|
|
+
|
|
+struct virtwl_ioctl_send {
|
|
+ int fds[VIRTWL_SEND_MAX_ALLOCS];
|
|
+ uint32_t len;
|
|
+ uint8_t data[0];
|
|
+};
|
|
+
|
|
+struct virtwl_ioctl_recv {
|
|
+ int fds[VIRTWL_SEND_MAX_ALLOCS];
|
|
+ uint32_t len;
|
|
+ uint8_t data[0];
|
|
+};
|
|
+
|
|
+#define VIRTWL_IOCTL_NEW VIRTWL_IOWR(0x00, struct virtwl_ioctl_new)
|
|
+#define VIRTWL_IOCTL_SEND VIRTWL_IOR(0x01, struct virtwl_ioctl_send)
|
|
+#define VIRTWL_IOCTL_RECV VIRTWL_IOW(0x02, struct virtwl_ioctl_recv)
|
|
+#define VIRTWL_IOCTL_MAXNR 3
|
|
+
|
|
+
|
|
+#endif /* _LINUX_VIRTWL_H */
|
|
+
|