pH/kernel/patches/0023-virtio-wayland.patch
2017-10-16 02:36:00 +00:00

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 */
+