1557 lines · 39.0 KB · C
RawDownload
/* Copyright (C) 2009 Red Hat, Inc. * Copyright (C) 2006 Rusty Russell IBM Corporation * * Author: Michael S. Tsirkin <mst@redhat.com> * * Inspiration, some code, and most witty comments come from * Documentation/virtual/lguest/lguest.c, by Rusty Russell * * This work is licensed under the terms of the GNU GPL, version 2. * * Generic code for virtio server in host kernel. */ #include <linux/eventfd.h>#include <linux/vhost.h>#include <linux/uio.h>#include <linux/mm.h>#include <linux/mmu_context.h>#include <linux/miscdevice.h>#include <linux/mutex.h>#include <linux/rcupdate.h>#include <linux/poll.h>#include <linux/file.h>#include <linux/highmem.h>#include <linux/slab.h>#include <linux/kthread.h>#include <linux/cgroup.h>#include <linux/module.h> #include "vhost.h" enum {	VHOST_MEMORY_MAX_NREGIONS = 64,	VHOST_MEMORY_F_LOG = 0x1,}; #define vhost_used_event(vq) ((u16 __user *)&vq->avail->ring[vq->num])#define vhost_avail_event(vq) ((u16 __user *)&vq->used->ring[vq->num]) static void vhost_poll_func(struct file *file, wait_queue_head_t *wqh,			    poll_table *pt){	struct vhost_poll *poll; 	poll = container_of(pt, struct vhost_poll, table);	poll->wqh = wqh;	add_wait_queue(wqh, &poll->wait);} static int vhost_poll_wakeup(wait_queue_t *wait, unsigned mode, int sync,			     void *key){	struct vhost_poll *poll = container_of(wait, struct vhost_poll, wait); 	if (!((unsigned long)key & poll->mask))		return 0; 	vhost_poll_queue(poll);	return 0;} void vhost_work_init(struct vhost_work *work, vhost_work_fn_t fn){	INIT_LIST_HEAD(&work->node);	work->fn = fn;	init_waitqueue_head(&work->done);	work->flushing = 0;	work->queue_seq = work->done_seq = 0;}EXPORT_SYMBOL_GPL(vhost_work_init); /* Init poll structure */void vhost_poll_init(struct vhost_poll *poll, vhost_work_fn_t fn,		     unsigned long mask, struct vhost_dev *dev){	init_waitqueue_func_entry(&poll->wait, vhost_poll_wakeup);	init_poll_funcptr(&poll->table, vhost_poll_func);	poll->mask = mask;	poll->dev = dev;	poll->wqh = NULL; 	vhost_work_init(&poll->work, fn);}EXPORT_SYMBOL_GPL(vhost_poll_init); /* Start polling a file. We add ourselves to file's wait queue. The caller must * keep a reference to a file until after vhost_poll_stop is called. */int vhost_poll_start(struct vhost_poll *poll, struct file *file){	unsigned long mask;	int ret = 0; 	if (poll->wqh)		return 0; 	mask = file->f_op->poll(file, &poll->table);	if (mask)		vhost_poll_wakeup(&poll->wait, 0, 0, (void *)mask);	if (mask & POLLERR) {		if (poll->wqh)			remove_wait_queue(poll->wqh, &poll->wait);		ret = -EINVAL;	} 	return ret;}EXPORT_SYMBOL_GPL(vhost_poll_start); /* Stop polling a file. After this function returns, it becomes safe to drop the * file reference. You must also flush afterwards. */void vhost_poll_stop(struct vhost_poll *poll){	if (poll->wqh) {		remove_wait_queue(poll->wqh, &poll->wait);		poll->wqh = NULL;	}}EXPORT_SYMBOL_GPL(vhost_poll_stop); static bool vhost_work_seq_done(struct vhost_dev *dev, struct vhost_work *work,				unsigned seq){	int left; 	spin_lock_irq(&dev->work_lock);	left = seq - work->done_seq;	spin_unlock_irq(&dev->work_lock);	return left <= 0;} void vhost_work_flush(struct vhost_dev *dev, struct vhost_work *work){	unsigned seq;	int flushing; 	spin_lock_irq(&dev->work_lock);	seq = work->queue_seq;	work->flushing++;	spin_unlock_irq(&dev->work_lock);	wait_event(work->done, vhost_work_seq_done(dev, work, seq));	spin_lock_irq(&dev->work_lock);	flushing = --work->flushing;	spin_unlock_irq(&dev->work_lock);	BUG_ON(flushing < 0);}EXPORT_SYMBOL_GPL(vhost_work_flush); /* Flush any work that has been scheduled. When calling this, don't hold any * locks that are also used by the callback. */void vhost_poll_flush(struct vhost_poll *poll){	vhost_work_flush(poll->dev, &poll->work);}EXPORT_SYMBOL_GPL(vhost_poll_flush); void vhost_work_queue(struct vhost_dev *dev, struct vhost_work *work){	unsigned long flags; 	spin_lock_irqsave(&dev->work_lock, flags);	if (list_empty(&work->node)) {		list_add_tail(&work->node, &dev->work_list);		work->queue_seq++;		spin_unlock_irqrestore(&dev->work_lock, flags);		wake_up_process(dev->worker);	} else {		spin_unlock_irqrestore(&dev->work_lock, flags);	}}EXPORT_SYMBOL_GPL(vhost_work_queue); void vhost_poll_queue(struct vhost_poll *poll){	vhost_work_queue(poll->dev, &poll->work);}EXPORT_SYMBOL_GPL(vhost_poll_queue); static void vhost_vq_reset(struct vhost_dev *dev,			   struct vhost_virtqueue *vq){	vq->num = 1;	vq->desc = NULL;	vq->avail = NULL;	vq->used = NULL;	vq->last_avail_idx = 0;	vq->avail_idx = 0;	vq->last_used_idx = 0;	vq->signalled_used = 0;	vq->signalled_used_valid = false;	vq->used_flags = 0;	vq->log_used = false;	vq->log_addr = -1ull;	vq->private_data = NULL;	vq->log_base = NULL;	vq->error_ctx = NULL;	vq->error = NULL;	vq->kick = NULL;	vq->call_ctx = NULL;	vq->call = NULL;	vq->log_ctx = NULL;} static int vhost_worker(void *data){	struct vhost_dev *dev = data;	struct vhost_work *work = NULL;	unsigned uninitialized_var(seq);	mm_segment_t oldfs = get_fs(); 	set_fs(USER_DS);	use_mm(dev->mm); 	for (;;) {		/* mb paired w/ kthread_stop */		set_current_state(TASK_INTERRUPTIBLE); 		spin_lock_irq(&dev->work_lock);		if (work) {			work->done_seq = seq;			if (work->flushing)				wake_up_all(&work->done);		} 		if (kthread_should_stop()) {			spin_unlock_irq(&dev->work_lock);			__set_current_state(TASK_RUNNING);			break;		}		if (!list_empty(&dev->work_list)) {			work = list_first_entry(&dev->work_list,						struct vhost_work, node);			list_del_init(&work->node);			seq = work->queue_seq;		} else			work = NULL;		spin_unlock_irq(&dev->work_lock); 		if (work) {			__set_current_state(TASK_RUNNING);			work->fn(work);			if (need_resched())				schedule();		} else			schedule(); 	}	unuse_mm(dev->mm);	set_fs(oldfs);	return 0;} static void vhost_vq_free_iovecs(struct vhost_virtqueue *vq){	kfree(vq->indirect);	vq->indirect = NULL;	kfree(vq->log);	vq->log = NULL;	kfree(vq->heads);	vq->heads = NULL;} /* Helper to allocate iovec buffers for all vqs. */static long vhost_dev_alloc_iovecs(struct vhost_dev *dev){	struct vhost_virtqueue *vq;	int i; 	for (i = 0; i < dev->nvqs; ++i) {		vq = dev->vqs[i];		vq->indirect = kmalloc(sizeof *vq->indirect * UIO_MAXIOV,				       GFP_KERNEL);		vq->log = kmalloc(sizeof *vq->log * UIO_MAXIOV, GFP_KERNEL);		vq->heads = kmalloc(sizeof *vq->heads * UIO_MAXIOV, GFP_KERNEL);		if (!vq->indirect || !vq->log || !vq->heads)			goto err_nomem;	}	return 0; err_nomem:	for (; i >= 0; --i)		vhost_vq_free_iovecs(dev->vqs[i]);	return -ENOMEM;} static void vhost_dev_free_iovecs(struct vhost_dev *dev){	int i; 	for (i = 0; i < dev->nvqs; ++i)		vhost_vq_free_iovecs(dev->vqs[i]);} long vhost_dev_init(struct vhost_dev *dev,		    struct vhost_virtqueue **vqs, int nvqs){	struct vhost_virtqueue *vq;	int i; 	dev->vqs = vqs;	dev->nvqs = nvqs;	mutex_init(&dev->mutex);	dev->log_ctx = NULL;	dev->log_file = NULL;	dev->memory = NULL;	dev->mm = NULL;	spin_lock_init(&dev->work_lock);	INIT_LIST_HEAD(&dev->work_list);	dev->worker = NULL; 	for (i = 0; i < dev->nvqs; ++i) {		vq = dev->vqs[i];		vq->log = NULL;		vq->indirect = NULL;		vq->heads = NULL;		vq->dev = dev;		mutex_init(&vq->mutex);		vhost_vq_reset(dev, vq);		if (vq->handle_kick)			vhost_poll_init(&vq->poll, vq->handle_kick,					POLLIN, dev);	} 	return 0;}EXPORT_SYMBOL_GPL(vhost_dev_init); /* Caller should have device mutex */long vhost_dev_check_owner(struct vhost_dev *dev){	/* Are you the owner? If not, I don't think you mean to do that */	return dev->mm == current->mm ? 0 : -EPERM;}EXPORT_SYMBOL_GPL(vhost_dev_check_owner); struct vhost_attach_cgroups_struct {	struct vhost_work work;	struct task_struct *owner;	int ret;}; static void vhost_attach_cgroups_work(struct vhost_work *work){	struct vhost_attach_cgroups_struct *s; 	s = container_of(work, struct vhost_attach_cgroups_struct, work);	s->ret = cgroup_attach_task_all(s->owner, current);} static int vhost_attach_cgroups(struct vhost_dev *dev){	struct vhost_attach_cgroups_struct attach; 	attach.owner = current;	vhost_work_init(&attach.work, vhost_attach_cgroups_work);	vhost_work_queue(dev, &attach.work);	vhost_work_flush(dev, &attach.work);	return attach.ret;} /* Caller should have device mutex */bool vhost_dev_has_owner(struct vhost_dev *dev){	return dev->mm;}EXPORT_SYMBOL_GPL(vhost_dev_has_owner); /* Caller should have device mutex */long vhost_dev_set_owner(struct vhost_dev *dev){	struct task_struct *worker;	int err; 	/* Is there an owner already? */	if (vhost_dev_has_owner(dev)) {		err = -EBUSY;		goto err_mm;	} 	/* No owner, become one */	dev->mm = get_task_mm(current);	worker = kthread_create(vhost_worker, dev, "vhost-%d", current->pid);	if (IS_ERR(worker)) {		err = PTR_ERR(worker);		goto err_worker;	} 	dev->worker = worker;	wake_up_process(worker);	/* avoid contributing to loadavg */ 	err = vhost_attach_cgroups(dev);	if (err)		goto err_cgroup; 	err = vhost_dev_alloc_iovecs(dev);	if (err)		goto err_cgroup; 	return 0;err_cgroup:	kthread_stop(worker);	dev->worker = NULL;err_worker:	if (dev->mm)		mmput(dev->mm);	dev->mm = NULL;err_mm:	return err;}EXPORT_SYMBOL_GPL(vhost_dev_set_owner); struct vhost_memory *vhost_dev_reset_owner_prepare(void){	return kmalloc(offsetof(struct vhost_memory, regions), GFP_KERNEL);}EXPORT_SYMBOL_GPL(vhost_dev_reset_owner_prepare); /* Caller should have device mutex */void vhost_dev_reset_owner(struct vhost_dev *dev, struct vhost_memory *memory){	vhost_dev_cleanup(dev, true); 	/* Restore memory to default empty mapping. */	memory->nregions = 0;	RCU_INIT_POINTER(dev->memory, memory);}EXPORT_SYMBOL_GPL(vhost_dev_reset_owner); void vhost_dev_stop(struct vhost_dev *dev){	int i; 	for (i = 0; i < dev->nvqs; ++i) {		if (dev->vqs[i]->kick && dev->vqs[i]->handle_kick) {			vhost_poll_stop(&dev->vqs[i]->poll);			vhost_poll_flush(&dev->vqs[i]->poll);		}	}}EXPORT_SYMBOL_GPL(vhost_dev_stop); /* Caller should have device mutex if and only if locked is set */void vhost_dev_cleanup(struct vhost_dev *dev, bool locked){	int i; 	for (i = 0; i < dev->nvqs; ++i) {		if (dev->vqs[i]->error_ctx)			eventfd_ctx_put(dev->vqs[i]->error_ctx);		if (dev->vqs[i]->error)			fput(dev->vqs[i]->error);		if (dev->vqs[i]->kick)			fput(dev->vqs[i]->kick);		if (dev->vqs[i]->call_ctx)			eventfd_ctx_put(dev->vqs[i]->call_ctx);		if (dev->vqs[i]->call)			fput(dev->vqs[i]->call);		vhost_vq_reset(dev, dev->vqs[i]);	}	vhost_dev_free_iovecs(dev);	if (dev->log_ctx)		eventfd_ctx_put(dev->log_ctx);	dev->log_ctx = NULL;	if (dev->log_file)		fput(dev->log_file);	dev->log_file = NULL;	/* No one will access memory at this point */	kfree(rcu_dereference_protected(dev->memory,					locked ==						lockdep_is_held(&dev->mutex)));	RCU_INIT_POINTER(dev->memory, NULL);	WARN_ON(!list_empty(&dev->work_list));	if (dev->worker) {		kthread_stop(dev->worker);		dev->worker = NULL;	}	if (dev->mm)		mmput(dev->mm);	dev->mm = NULL;}EXPORT_SYMBOL_GPL(vhost_dev_cleanup); static int log_access_ok(void __user *log_base, u64 addr, unsigned long sz){	u64 a = addr / VHOST_PAGE_SIZE / 8; 	/* Make sure 64 bit math will not overflow. */	if (a > ULONG_MAX - (unsigned long)log_base ||	    a + (unsigned long)log_base > ULONG_MAX)		return 0; 	return access_ok(VERIFY_WRITE, log_base + a,			 (sz + VHOST_PAGE_SIZE * 8 - 1) / VHOST_PAGE_SIZE / 8);} /* Caller should have vq mutex and device mutex. */static int vq_memory_access_ok(void __user *log_base, struct vhost_memory *mem,			       int log_all){	int i; 

Lines 1–500 of 1557.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t