From ee3ae46ee497e111f6f4afbe3635f461e991c1e9 Mon Sep 17 00:00:00 2001 From: Zach Reizner 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 Tested-by: Zach Reizner Reviewed-by: Zach Reizner --- 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 +#include +#include + + +#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 + +#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 */ +