summaryrefslogblamecommitdiffstats
path: root/drivers/staging/android/vsoc.c
blob: 00a1ec7b915492b00dfc58e9b599b264e1a7d7dd (plain) (tree)
1
2
3
4
5
6
7
8
9
10
                                   








                                                 




















                                                                              









































                                                                               

                            







                                                          
                                        























                                                                                



                                                      
                                                     
                         
                                                 
                            








                                                                              




















                                                                               

                                                                                
                                                                       






                                                                            


































                                                                               
                                                                   











                                                                     

                                                                             



                                                

                                                                            






































                                                                            



                                                                           





                                                    

                                                                 


















































































                                                                                


                                                                               










                                                                   


                                                                          





                                   

                                                                           



































                                                                               
                          


























                                                                              
                                                        
                                       

                                                                               

                                                                  
                                                                   














































































































                                                                               



                                                                                

























                                                                              
                                                                    









                                                                  
                                              


                                                                           


                                  


                                                                  

















                                                                              
                                                                            


                                         
                           




































































                                                                              
                                                                       
































































                                                                       
                                       





                                                                    
                                          









                                                                        
                                                             








                                                                              

                                                                    
                                                                              





                                                                     

                                                                       





























                                                                               
                                   





                                                                     


                                                                
                                                           

                                                      






                                                                      

                                                      














































                                                                               
                                     












                                                                               

                                                                         

                                                                                
                                                                              

                                                                        
                                                                              
                                                                        



                                                                     






                                                                         
                                                              






                                                                              
                                                               






























                                                                           
                                                                                


                                                                                
                                                                       

                                             
                                             


                                       
                                              

                                     

                                     

                                              
                                             


                                         
                                            





                                                                        
                               

                                                              
                                                  


                                                 
                                     


                                          
                                                   


                                         
                                                



































































































































                                                                               
// SPDX-License-Identifier: GPL-2.0
/*
 * drivers/android/staging/vsoc.c
 *
 * Android Virtual System on a Chip (VSoC) driver
 *
 * Copyright (C) 2017 Google, Inc.
 *
 * Author: ghartman@google.com
 *
 * Based on drivers/char/kvm_ivshmem.c - driver for KVM Inter-VM shared memory
 *         Copyright 2009 Cam Macdonell <cam@cs.ualberta.ca>
 *
 * Based on cirrusfb.c and 8139cp.c:
 *   Copyright 1999-2001 Jeff Garzik
 *   Copyright 2001-2004 Jeff Garzik
 */

#include <linux/dma-mapping.h>
#include <linux/freezer.h>
#include <linux/futex.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/proc_fs.h>
#include <linux/sched.h>
#include <linux/syscalls.h>
#include <linux/uaccess.h>
#include <linux/interrupt.h>
#include <linux/cdev.h>
#include <linux/file.h>
#include "uapi/vsoc_shm.h"

#define VSOC_DEV_NAME "vsoc"

/*
 * Description of the ivshmem-doorbell PCI device used by QEmu. These
 * constants follow docs/specs/ivshmem-spec.txt, which can be found in
 * the QEmu repository. This was last reconciled with the version that
 * came out with 2.8
 */

/*
 * These constants are determined KVM Inter-VM shared memory device
 * register offsets
 */
enum {
	INTR_MASK = 0x00,	/* Interrupt Mask */
	INTR_STATUS = 0x04,	/* Interrupt Status */
	IV_POSITION = 0x08,	/* VM ID */
	DOORBELL = 0x0c,	/* Doorbell */
};

static const int REGISTER_BAR;  /* Equal to 0 */
static const int MAX_REGISTER_BAR_LEN = 0x100;
/*
 * The MSI-x BAR is not used directly.
 *
 * static const int MSI_X_BAR = 1;
 */
static const int SHARED_MEMORY_BAR = 2;

struct vsoc_region_data {
	char name[VSOC_DEVICE_NAME_SZ + 1];
	wait_queue_head_t interrupt_wait_queue;
	/* TODO(b/73664181): Use multiple futex wait queues */
	wait_queue_head_t futex_wait_queue;
	/* Flag indicating that an interrupt has been signalled by the host. */
	atomic_t *incoming_signalled;
	/* Flag indicating the guest has signalled the host. */
	atomic_t *outgoing_signalled;
	bool irq_requested;
	bool device_created;
};

struct vsoc_device {
	/* Kernel virtual address of REGISTER_BAR. */
	void __iomem *regs;
	/* Physical address of SHARED_MEMORY_BAR. */
	phys_addr_t shm_phys_start;
	/* Kernel virtual address of SHARED_MEMORY_BAR. */
	void __iomem *kernel_mapped_shm;
	/* Size of the entire shared memory window in bytes. */
	size_t shm_size;
	/*
	 * Pointer to the virtual address of the shared memory layout structure.
	 * This is probably identical to kernel_mapped_shm, but saving this
	 * here saves a lot of annoying casts.
	 */
	struct vsoc_shm_layout_descriptor *layout;
	/*
	 * Points to a table of region descriptors in the kernel's virtual
	 * address space. Calculated from
	 * vsoc_shm_layout_descriptor.vsoc_region_desc_offset
	 */
	struct vsoc_device_region *regions;
	/* Head of a list of permissions that have been granted. */
	struct list_head permissions;
	struct pci_dev *dev;
	/* Per-region (and therefore per-interrupt) information. */
	struct vsoc_region_data *regions_data;
	/*
	 * Table of msi-x entries. This has to be separated from struct
	 * vsoc_region_data because the kernel deals with them as an array.
	 */
	struct msix_entry *msix_entries;
	/* Mutex that protectes the permission list */
	struct mutex mtx;
	/* Major number assigned by the kernel */
	int major;
	/* Character device assigned by the kernel */
	struct cdev cdev;
	/* Device class assigned by the kernel */
	struct class *class;
	/*
	 * Flags that indicate what we've initialized. These are used to do an
	 * orderly cleanup of the device.
	 */
	bool enabled_device;
	bool requested_regions;
	bool cdev_added;
	bool class_added;
	bool msix_enabled;
};

static struct vsoc_device vsoc_dev;

/*
 * TODO(ghartman): Add a /sys filesystem entry that summarizes the permissions.
 */

struct fd_scoped_permission_node {
	struct fd_scoped_permission permission;
	struct list_head list;
};

struct vsoc_private_data {
	struct fd_scoped_permission_node *fd_scoped_permission_node;
};

static long vsoc_ioctl(struct file *, unsigned int, unsigned long);
static int vsoc_mmap(struct file *, struct vm_area_struct *);
static int vsoc_open(struct inode *, struct file *);
static int vsoc_release(struct inode *, struct file *);
static ssize_t vsoc_read(struct file *, char __user *, size_t, loff_t *);
static ssize_t vsoc_write(struct file *, const char __user *, size_t, loff_t *);
static loff_t vsoc_lseek(struct file *filp, loff_t offset, int origin);
static int
do_create_fd_scoped_permission(struct vsoc_device_region *region_p,
			       struct fd_scoped_permission_node *np,
			       struct fd_scoped_permission_arg __user *arg);
static void
do_destroy_fd_scoped_permission(struct vsoc_device_region *owner_region_p,
				struct fd_scoped_permission *perm);
static long do_vsoc_describe_region(struct file *,
				    struct vsoc_device_region __user *);
static ssize_t vsoc_get_area(struct file *filp, __u32 *perm_off);

/**
 * Validate arguments on entry points to the driver.
 */
inline int vsoc_validate_inode(struct inode *inode)
{
	if (iminor(inode) >= vsoc_dev.layout->region_count) {
		dev_err(&vsoc_dev.dev->dev,
			"describe_region: invalid region %d\n", iminor(inode));
		return -ENODEV;
	}
	return 0;
}

inline int vsoc_validate_filep(struct file *filp)
{
	int ret = vsoc_validate_inode(file_inode(filp));

	if (ret)
		return ret;
	if (!filp->private_data) {
		dev_err(&vsoc_dev.dev->dev,
			"No private data on fd, region %d\n",
			iminor(file_inode(filp)));
		return -EBADFD;
	}
	return 0;
}

/* Converts from shared memory offset to virtual address */
static inline void *shm_off_to_virtual_addr(__u32 offset)
{
	return (void __force *)vsoc_dev.kernel_mapped_shm + offset;
}

/* Converts from shared memory offset to physical address */
static inline phys_addr_t shm_off_to_phys_addr(__u32 offset)
{
	return vsoc_dev.shm_phys_start + offset;
}

/**
 * Convenience functions to obtain the region from the inode or file.
 * Dangerous to call before validating the inode/file.
 */
static
inline struct vsoc_device_region *vsoc_region_from_inode(struct inode *inode)
{
	return &vsoc_dev.regions[iminor(inode)];
}

static
inline struct vsoc_device_region *vsoc_region_from_filep(struct file *inode)
{
	return vsoc_region_from_inode(file_inode(inode));
}

static inline uint32_t vsoc_device_region_size(struct vsoc_device_region *r)
{
	return r->region_end_offset - r->region_begin_offset;
}

static const struct file_operations vsoc_ops = {
	.owner = THIS_MODULE,
	.open = vsoc_open,
	.mmap = vsoc_mmap,
	.read = vsoc_read,
	.unlocked_ioctl = vsoc_ioctl,
	.compat_ioctl = vsoc_ioctl,
	.write = vsoc_write,
	.llseek = vsoc_lseek,
	.release = vsoc_release,
};

static struct pci_device_id vsoc_id_table[] = {
	{0x1af4, 0x1110, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
	{0},
};

MODULE_DEVICE_TABLE(pci, vsoc_id_table);

static void vsoc_remove_device(struct pci_dev *pdev);
static int vsoc_probe_device(struct pci_dev *pdev,
			     const struct pci_device_id *ent);

static struct pci_driver vsoc_pci_driver = {
	.name = "vsoc",
	.id_table = vsoc_id_table,
	.probe = vsoc_probe_device,
	.remove = vsoc_remove_device,
};

static int
do_create_fd_scoped_permission(struct vsoc_device_region *region_p,
			       struct fd_scoped_permission_node *np,
			       struct fd_scoped_permission_arg __user *arg)
{
	struct file *managed_filp;
	s32 managed_fd;
	atomic_t *owner_ptr = NULL;
	struct vsoc_device_region *managed_region_p;

	if (copy_from_user(&np->permission,
			   &arg->perm, sizeof(np->permission)) ||
	    copy_from_user(&managed_fd,
			   &arg->managed_region_fd, sizeof(managed_fd))) {
		return -EFAULT;
	}
	managed_filp = fdget(managed_fd).file;
	/* Check that it's a valid fd, */
	if (!managed_filp || vsoc_validate_filep(managed_filp))
		return -EPERM;
	/* EEXIST if the given fd already has a permission. */
	if (((struct vsoc_private_data *)managed_filp->private_data)->
	    fd_scoped_permission_node)
		return -EEXIST;
	managed_region_p = vsoc_region_from_filep(managed_filp);
	/* Check that the provided region is managed by this one */
	if (&vsoc_dev.regions[managed_region_p->managed_by] != region_p)
		return -EPERM;
	/* The area must be well formed and have non-zero size */
	if (np->permission.begin_offset >= np->permission.end_offset)
		return -EINVAL;
	/* The area must fit in the memory window */
	if (np->permission.end_offset >
	    vsoc_device_region_size(managed_region_p))
		return -ERANGE;
	/* The area must be in the region data section */
	if (np->permission.begin_offset <
	    managed_region_p->offset_of_region_data)
		return -ERANGE;
	/* The area must be page aligned */
	if (!PAGE_ALIGNED(np->permission.begin_offset) ||
	    !PAGE_ALIGNED(np->permission.end_offset))
		return -EINVAL;
	/* Owner offset must be naturally aligned in the window */
	if (np->permission.owner_offset &
	    (sizeof(np->permission.owner_offset) - 1))
		return -EINVAL;
	/* The owner flag must reside in the owner memory */
	if (np->permission.owner_offset + sizeof(np->permission.owner_offset) >
	    vsoc_device_region_size(region_p))
		return -ERANGE;
	/* The owner flag must reside in the data section */
	if (np->permission.owner_offset < region_p->offset_of_region_data)
		return -EINVAL;
	/* The owner value must change to claim the memory */
	if (np->permission.owned_value == VSOC_REGION_FREE)
		return -EINVAL;
	owner_ptr =
	    (atomic_t *)shm_off_to_virtual_addr(region_p->region_begin_offset +
						np->permission.owner_offset);
	/* We've already verified that this is in the shared memory window, so
	 * it should be safe to write to this address.
	 */
	if (atomic_cmpxchg(owner_ptr,
			   VSOC_REGION_FREE,
			   np->permission.owned_value) != VSOC_REGION_FREE) {
		return -EBUSY;
	}
	((struct vsoc_private_data *)managed_filp->private_data)->
	    fd_scoped_permission_node = np;
	/* The file offset needs to be adjusted if the calling
	 * process did any read/write operations on the fd
	 * before creating the permission.
	 */
	if (managed_filp->f_pos) {
		if (managed_filp->f_pos > np->permission.end_offset) {
			/* If the offset is beyond the permission end, set it
			 * to the end.
			 */
			managed_filp->f_pos = np->permission.end_offset;
		} else {
			/* If the offset is within the permission interval
			 * keep it there otherwise reset it to zero.
			 */
			if (managed_filp->f_pos < np->permission.begin_offset) {
				managed_filp->f_pos = 0;
			} else {
				managed_filp->f_pos -=
				    np->permission.begin_offset;
			}
		}
	}
	return 0;
}

static void
do_destroy_fd_scoped_permission_node(struct vsoc_device_region *owner_region_p,
				     struct fd_scoped_permission_node *node)
{
	if (node) {
		do_destroy_fd_scoped_permission(owner_region_p,
						&node->permission);
		mutex_lock(&vsoc_dev.mtx);
		list_del(&node->list);
		mutex_unlock(&vsoc_dev.mtx);
		kfree(node);
	}
}

static void
do_destroy_fd_scoped_permission(struct vsoc_device_region *owner_region_p,
				struct fd_scoped_permission *perm)
{
	atomic_t *owner_ptr = NULL;
	int prev = 0;

	if (!perm)
		return;
	owner_ptr = (atomic_t *)shm_off_to_virtual_addr
		(owner_region_p->region_begin_offset + perm->owner_offset);
	prev = atomic_xchg(owner_ptr, VSOC_REGION_FREE);
	if (prev != perm->owned_value)
		dev_err(&vsoc_dev.dev->dev,
			"%x-%x: owner (%s) %x: expected to be %x was %x",
			perm->begin_offset, perm->end_offset,
			owner_region_p->device_name, perm->owner_offset,
			perm->owned_value, prev);
}

static long do_vsoc_describe_region(struct file *filp,
				    struct vsoc_device_region __user *dest)
{
	struct vsoc_device_region *region_p;
	int retval = vsoc_validate_filep(filp);

	if (retval)
		return retval;
	region_p = vsoc_region_from_filep(filp);
	if (copy_to_user(dest, region_p, sizeof(*region_p)))
		return -EFAULT;
	return 0;
}

/**
 * Implements the inner logic of cond_wait. Copies to and from userspace are
 * done in the helper function below.
 */
static int handle_vsoc_cond_wait(struct file *filp, struct vsoc_cond_wait *arg)
{
	DEFINE_WAIT(wait);
	u32 region_number = iminor(file_inode(filp));
	struct vsoc_region_data *data = vsoc_dev.regions_data + region_number;
	struct hrtimer_sleeper timeout, *to = NULL;
	int ret = 0;
	struct vsoc_device_region *region_p = vsoc_region_from_filep(filp);
	atomic_t *address = NULL;
	ktime_t wake_time;

	/* Ensure that the offset is aligned */
	if (arg->offset & (sizeof(uint32_t) - 1))
		return -EADDRNOTAVAIL;
	/* Ensure that the offset is within shared memory */
	if (((uint64_t)arg->offset) + region_p->region_begin_offset +
	    sizeof(uint32_t) > region_p->region_end_offset)
		return -E2BIG;
	address = shm_off_to_virtual_addr(region_p->region_begin_offset +
					  arg->offset);

	/* Ensure that the type of wait is valid */
	switch (arg->wait_type) {
	case VSOC_WAIT_IF_EQUAL:
		break;
	case VSOC_WAIT_IF_EQUAL_TIMEOUT:
		to = &timeout;
		break;
	default:
		return -EINVAL;
	}

	if (to) {
		/* Copy the user-supplied timesec into the kernel structure.
		 * We do things this way to flatten differences between 32 bit
		 * and 64 bit timespecs.
		 */
		if (arg->wake_time_nsec >= NSEC_PER_SEC)
			return -EINVAL;
		wake_time = ktime_set(arg->wake_time_sec, arg->wake_time_nsec);

		hrtimer_init_on_stack(&to->timer, CLOCK_MONOTONIC,
				      HRTIMER_MODE_ABS);
		hrtimer_set_expires_range_ns(&to->timer, wake_time,
					     current->timer_slack_ns);

		hrtimer_init_sleeper(to, current);
	}

	while (1) {
		prepare_to_wait(&data->futex_wait_queue, &wait,
				TASK_INTERRUPTIBLE);
		/*
		 * Check the sentinel value after prepare_to_wait. If the value
		 * changes after this check the writer will call signal,
		 * changing the task state from INTERRUPTIBLE to RUNNING. That
		 * will ensure that schedule() will eventually schedule this
		 * task.
		 */
		if (atomic_read(address) != arg->value) {
			ret = 0;
			break;
		}
		if (to) {
			hrtimer_start_expires(&to->timer, HRTIMER_MODE_ABS);
			if (likely(to->task))
				freezable_schedule();
			hrtimer_cancel(&to->timer);
			if (!to->task) {
				ret = -ETIMEDOUT;
				break;
			}
		} else {
			freezable_schedule();
		}
		/* Count the number of times that we woke up. This is useful
		 * for unit testing.
		 */
		++arg->wakes;
		if (signal_pending(current)) {
			ret = -EINTR;
			break;
		}
	}
	finish_wait(&data->futex_wait_queue, &wait);
	if (to)
		destroy_hrtimer_on_stack(&to->timer);
	return ret;
}

/**
 * Handles the details of copying from/to userspace to ensure that the copies
 * happen on all of the return paths of cond_wait.
 */
static int do_vsoc_cond_wait(struct file *filp,
			     struct vsoc_cond_wait __user *untrusted_in)
{
	struct vsoc_cond_wait arg;
	int rval = 0;

	if (copy_from_user(&arg, untrusted_in, sizeof(arg)))
		return -EFAULT;
	/* wakes is an out parameter. Initialize it to something sensible. */
	arg.wakes = 0;
	rval = handle_vsoc_cond_wait(filp, &arg);
	if (copy_to_user(untrusted_in, &arg, sizeof(arg)))
		return -EFAULT;
	return rval;
}

static int do_vsoc_cond_wake(struct file *filp, uint32_t offset)
{
	struct vsoc_device_region *region_p = vsoc_region_from_filep(filp);
	u32 region_number = iminor(file_inode(filp));
	struct vsoc_region_data *data = vsoc_dev.regions_data + region_number;
	/* Ensure that the offset is aligned */
	if (offset & (sizeof(uint32_t) - 1))
		return -EADDRNOTAVAIL;
	/* Ensure that the offset is within shared memory */
	if (((uint64_t)offset) + region_p->region_begin_offset +
	    sizeof(uint32_t) > region_p->region_end_offset)
		return -E2BIG;
	/*
	 * TODO(b/73664181): Use multiple futex wait queues.
	 * We need to wake every sleeper when the condition changes. Typically
	 * only a single thread will be waiting on the condition, but there
	 * are exceptions. The worst case is about 10 threads.
	 */
	wake_up_interruptible_all(&data->futex_wait_queue);
	return 0;
}

static long vsoc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	int rv = 0;
	struct vsoc_device_region *region_p;
	u32 reg_num;
	struct vsoc_region_data *reg_data;
	int retval = vsoc_validate_filep(filp);

	if (retval)
		return retval;
	region_p = vsoc_region_from_filep(filp);
	reg_num = iminor(file_inode(filp));
	reg_data = vsoc_dev.regions_data + reg_num;
	switch (cmd) {
	case VSOC_CREATE_FD_SCOPED_PERMISSION:
		{
			struct fd_scoped_permission_node *node = NULL;

			node = kzalloc(sizeof(*node), GFP_KERNEL);
			/* We can't allocate memory for the permission */
			if (!node)
				return -ENOMEM;
			INIT_LIST_HEAD(&node->list);
			rv = do_create_fd_scoped_permission
				(region_p,
				 node,
				 (struct fd_scoped_permission_arg __user *)arg);
			if (!rv) {
				mutex_lock(&vsoc_dev.mtx);
				list_add(&node->list, &vsoc_dev.permissions);
				mutex_unlock(&vsoc_dev.mtx);
			} else {
				kfree(node);
				return rv;
			}
		}
		break;

	case VSOC_GET_FD_SCOPED_PERMISSION:
		{
			struct fd_scoped_permission_node *node =
			    ((struct vsoc_private_data *)filp->private_data)->
			    fd_scoped_permission_node;
			if (!node)
				return -ENOENT;
			if (copy_to_user
			    ((struct fd_scoped_permission __user *)arg,
			     &node->permission, sizeof(node->permission)))
				return -EFAULT;
		}
		break;

	case VSOC_MAYBE_SEND_INTERRUPT_TO_HOST:
		if (!atomic_xchg(reg_data->outgoing_signalled, 1)) {
			writel(reg_num, vsoc_dev.regs + DOORBELL);
			return 0;
		} else {
			return -EBUSY;
		}
		break;

	case VSOC_SEND_INTERRUPT_TO_HOST:
		writel(reg_num, vsoc_dev.regs + DOORBELL);
		return 0;
	case VSOC_WAIT_FOR_INCOMING_INTERRUPT:
		wait_event_interruptible
			(reg_data->interrupt_wait_queue,
			 (atomic_read(reg_data->incoming_signalled) != 0));
		break;

	case VSOC_DESCRIBE_REGION:
		return do_vsoc_describe_region
			(filp,
			 (struct vsoc_device_region __user *)arg);

	case VSOC_SELF_INTERRUPT:
		atomic_set(reg_data->incoming_signalled, 1);
		wake_up_interruptible(&reg_data->interrupt_wait_queue);
		break;

	case VSOC_COND_WAIT:
		return do_vsoc_cond_wait(filp,
					 (struct vsoc_cond_wait __user *)arg);
	case VSOC_COND_WAKE:
		return do_vsoc_cond_wake(filp, arg);

	default:
		return -EINVAL;
	}
	return 0;
}

static ssize_t vsoc_read(struct file *filp, char __user *buffer, size_t len,
			 loff_t *poffset)
{
	__u32 area_off;
	const void *area_p;
	ssize_t area_len;
	int retval = vsoc_validate_filep(filp);

	if (retval)
		return retval;
	area_len = vsoc_get_area(filp, &area_off);
	area_p = shm_off_to_virtual_addr(area_off);
	area_p += *poffset;
	area_len -= *poffset;
	if (area_len <= 0)
		return 0;
	if (area_len < len)
		len = area_len;
	if (copy_to_user(buffer, area_p, len))
		return -EFAULT;
	*poffset += len;
	return len;
}

static loff_t vsoc_lseek(struct file *filp, loff_t offset, int origin)
{
	ssize_t area_len = 0;
	int retval = vsoc_validate_filep(filp);

	if (retval)
		return retval;
	area_len = vsoc_get_area(filp, NULL);
	switch (origin) {
	case SEEK_SET:
		break;

	case SEEK_CUR:
		if (offset > 0 && offset + filp->f_pos < 0)
			return -EOVERFLOW;
		offset += filp->f_pos;
		break;

	case SEEK_END:
		if (offset > 0 && offset + area_len < 0)
			return -EOVERFLOW;
		offset += area_len;
		break;

	case SEEK_DATA:
		if (offset >= area_len)
			return -EINVAL;
		if (offset < 0)
			offset = 0;
		break;

	case SEEK_HOLE:
		/* Next hole is always the end of the region, unless offset is
		 * beyond that
		 */
		if (offset < area_len)
			offset = area_len;
		break;

	default:
		return -EINVAL;
	}

	if (offset < 0 || offset > area_len)
		return -EINVAL;
	filp->f_pos = offset;

	return offset;
}

static ssize_t vsoc_write(struct file *filp, const char __user *buffer,
			  size_t len, loff_t *poffset)
{
	__u32 area_off;
	void *area_p;
	ssize_t area_len;
	int retval = vsoc_validate_filep(filp);

	if (retval)
		return retval;
	area_len = vsoc_get_area(filp, &area_off);
	area_p = shm_off_to_virtual_addr(area_off);
	area_p += *poffset;
	area_len -= *poffset;
	if (area_len <= 0)
		return 0;
	if (area_len < len)
		len = area_len;
	if (copy_from_user(area_p, buffer, len))
		return -EFAULT;
	*poffset += len;
	return len;
}

static irqreturn_t vsoc_interrupt(int irq, void *region_data_v)
{
	struct vsoc_region_data *region_data =
	    (struct vsoc_region_data *)region_data_v;
	int reg_num = region_data - vsoc_dev.regions_data;

	if (unlikely(!region_data))
		return IRQ_NONE;

	if (unlikely(reg_num < 0 ||
		     reg_num >= vsoc_dev.layout->region_count)) {
		dev_err(&vsoc_dev.dev->dev,
			"invalid irq @%p reg_num=0x%04x\n",
			region_data, reg_num);
		return IRQ_NONE;
	}
	if (unlikely(vsoc_dev.regions_data + reg_num != region_data)) {
		dev_err(&vsoc_dev.dev->dev,
			"irq not aligned @%p reg_num=0x%04x\n",
			region_data, reg_num);
		return IRQ_NONE;
	}
	wake_up_interruptible(&region_data->interrupt_wait_queue);
	return IRQ_HANDLED;
}

static int vsoc_probe_device(struct pci_dev *pdev,
			     const struct pci_device_id *ent)
{
	int result;
	int i;
	resource_size_t reg_size;
	dev_t devt;

	vsoc_dev.dev = pdev;
	result = pci_enable_device(pdev);
	if (result) {
		dev_err(&pdev->dev,
			"pci_enable_device failed %s: error %d\n",
			pci_name(pdev), result);
		return result;
	}
	vsoc_dev.enabled_device = true;
	result = pci_request_regions(pdev, "vsoc");
	if (result < 0) {
		dev_err(&pdev->dev, "pci_request_regions failed\n");
		vsoc_remove_device(pdev);
		return -EBUSY;
	}
	vsoc_dev.requested_regions = true;
	/* Set up the control registers in BAR 0 */
	reg_size = pci_resource_len(pdev, REGISTER_BAR);
	if (reg_size > MAX_REGISTER_BAR_LEN)
		vsoc_dev.regs =
		    pci_iomap(pdev, REGISTER_BAR, MAX_REGISTER_BAR_LEN);
	else
		vsoc_dev.regs = pci_iomap(pdev, REGISTER_BAR, reg_size);

	if (!vsoc_dev.regs) {
		dev_err(&pdev->dev,
			"cannot map registers of size %zu\n",
		       (size_t)reg_size);
		vsoc_remove_device(pdev);
		return -EBUSY;
	}

	/* Map the shared memory in BAR 2 */
	vsoc_dev.shm_phys_start = pci_resource_start(pdev, SHARED_MEMORY_BAR);
	vsoc_dev.shm_size = pci_resource_len(pdev, SHARED_MEMORY_BAR);

	dev_info(&pdev->dev, "shared memory @ DMA %pa size=0x%zx\n",
		 &vsoc_dev.shm_phys_start, vsoc_dev.shm_size);
	vsoc_dev.kernel_mapped_shm = pci_iomap_wc(pdev, SHARED_MEMORY_BAR, 0);
	if (!vsoc_dev.kernel_mapped_shm) {
		dev_err(&vsoc_dev.dev->dev, "cannot iomap region\n");
		vsoc_remove_device(pdev);
		return -EBUSY;
	}

	vsoc_dev.layout = (struct vsoc_shm_layout_descriptor __force *)
				vsoc_dev.kernel_mapped_shm;
	dev_info(&pdev->dev, "major_version: %d\n",
		 vsoc_dev.layout->major_version);
	dev_info(&pdev->dev, "minor_version: %d\n",
		 vsoc_dev.layout->minor_version);
	dev_info(&pdev->dev, "size: 0x%x\n", vsoc_dev.layout->size);
	dev_info(&pdev->dev, "regions: %d\n", vsoc_dev.layout->region_count);
	if (vsoc_dev.layout->major_version !=
	    CURRENT_VSOC_LAYOUT_MAJOR_VERSION) {
		dev_err(&vsoc_dev.dev->dev,
			"driver supports only major_version %d\n",
			CURRENT_VSOC_LAYOUT_MAJOR_VERSION);
		vsoc_remove_device(pdev);
		return -EBUSY;
	}
	result = alloc_chrdev_region(&devt, 0, vsoc_dev.layout->region_count,
				     VSOC_DEV_NAME);
	if (result) {
		dev_err(&vsoc_dev.dev->dev, "alloc_chrdev_region failed\n");
		vsoc_remove_device(pdev);
		return -EBUSY;
	}
	vsoc_dev.major = MAJOR(devt);
	cdev_init(&vsoc_dev.cdev, &vsoc_ops);
	vsoc_dev.cdev.owner = THIS_MODULE;
	result = cdev_add(&vsoc_dev.cdev, devt, vsoc_dev.layout->region_count);
	if (result) {
		dev_err(&vsoc_dev.dev->dev, "cdev_add error\n");
		vsoc_remove_device(pdev);
		return -EBUSY;
	}
	vsoc_dev.cdev_added = true;
	vsoc_dev.class = class_create(THIS_MODULE, VSOC_DEV_NAME);
	if (IS_ERR(vsoc_dev.class)) {
		dev_err(&vsoc_dev.dev->dev, "class_create failed\n");
		vsoc_remove_device(pdev);
		return PTR_ERR(vsoc_dev.class);
	}
	vsoc_dev.class_added = true;
	vsoc_dev.regions = (struct vsoc_device_region __force *)
		((void *)vsoc_dev.layout +
		 vsoc_dev.layout->vsoc_region_desc_offset);
	vsoc_dev.msix_entries =
		kcalloc(vsoc_dev.layout->region_count,
			sizeof(vsoc_dev.msix_entries[0]), GFP_KERNEL);
	if (!vsoc_dev.msix_entries) {
		dev_err(&vsoc_dev.dev->dev,
			"unable to allocate msix_entries\n");
		vsoc_remove_device(pdev);
		return -ENOSPC;
	}
	vsoc_dev.regions_data =
		kcalloc(vsoc_dev.layout->region_count,
			sizeof(vsoc_dev.regions_data[0]), GFP_KERNEL);
	if (!vsoc_dev.regions_data) {
		dev_err(&vsoc_dev.dev->dev,
			"unable to allocate regions' data\n");
		vsoc_remove_device(pdev);
		return -ENOSPC;
	}
	for (i = 0; i < vsoc_dev.layout->region_count; ++i)
		vsoc_dev.msix_entries[i].entry = i;

	result = pci_enable_msix_exact(vsoc_dev.dev, vsoc_dev.msix_entries,
				       vsoc_dev.layout->region_count);
	if (result) {
		dev_info(&pdev->dev, "pci_enable_msix failed: %d\n", result);
		vsoc_remove_device(pdev);
		return -ENOSPC;
	}
	/* Check that all regions are well formed */
	for (i = 0; i < vsoc_dev.layout->region_count; ++i) {
		const struct vsoc_device_region *region = vsoc_dev.regions + i;

		if (!PAGE_ALIGNED(region->region_begin_offset) ||
		    !PAGE_ALIGNED(region->region_end_offset)) {
			dev_err(&vsoc_dev.dev->dev,
				"region %d not aligned (%x:%x)", i,
				region->region_begin_offset,
				region->region_end_offset);
			vsoc_remove_device(pdev);
			return -EFAULT;
		}
		if (region->region_begin_offset >= region->region_end_offset ||
		    region->region_end_offset > vsoc_dev.shm_size) {
			dev_err(&vsoc_dev.dev->dev,
				"region %d offsets are wrong: %x %x %zx",
				i, region->region_begin_offset,
				region->region_end_offset, vsoc_dev.shm_size);
			vsoc_remove_device(pdev);
			return -EFAULT;
		}
		if (region->managed_by >= vsoc_dev.layout->region_count) {
			dev_err(&vsoc_dev.dev->dev,
				"region %d has invalid owner: %u",
				i, region->managed_by);
			vsoc_remove_device(pdev);
			return -EFAULT;
		}
	}
	vsoc_dev.msix_enabled = true;
	for (i = 0; i < vsoc_dev.layout->region_count; ++i) {
		const struct vsoc_device_region *region = vsoc_dev.regions + i;
		size_t name_sz = sizeof(vsoc_dev.regions_data[i].name) - 1;
		const struct vsoc_signal_table_layout *h_to_g_signal_table =
			&region->host_to_guest_signal_table;
		const struct vsoc_signal_table_layout *g_to_h_signal_table =
			&region->guest_to_host_signal_table;

		vsoc_dev.regions_data[i].name[name_sz] = '\0';
		memcpy(vsoc_dev.regions_data[i].name, region->device_name,
		       name_sz);
		dev_info(&pdev->dev, "region %d name=%s\n",
			 i, vsoc_dev.regions_data[i].name);
		init_waitqueue_head
			(&vsoc_dev.regions_data[i].interrupt_wait_queue);
		init_waitqueue_head(&vsoc_dev.regions_data[i].futex_wait_queue);
		vsoc_dev.regions_data[i].incoming_signalled =
			shm_off_to_virtual_addr(region->region_begin_offset) +
			h_to_g_signal_table->interrupt_signalled_offset;
		vsoc_dev.regions_data[i].outgoing_signalled =
			shm_off_to_virtual_addr(region->region_begin_offset) +
			g_to_h_signal_table->interrupt_signalled_offset;
		result = request_irq(vsoc_dev.msix_entries[i].vector,
				     vsoc_interrupt, 0,
				     vsoc_dev.regions_data[i].name,
				     vsoc_dev.regions_data + i);
		if (result) {
			dev_info(&pdev->dev,
				 "request_irq failed irq=%d vector=%d\n",
				i, vsoc_dev.msix_entries[i].vector);
			vsoc_remove_device(pdev);
			return -ENOSPC;
		}
		vsoc_dev.regions_data[i].irq_requested = true;
		if (!device_create(vsoc_dev.class, NULL,
				   MKDEV(vsoc_dev.major, i),
				   NULL, vsoc_dev.regions_data[i].name)) {
			dev_err(&vsoc_dev.dev->dev, "device_create failed\n");
			vsoc_remove_device(pdev);
			return -EBUSY;
		}
		vsoc_dev.regions_data[i].device_created = true;
	}
	return 0;
}

/*
 * This should undo all of the allocations in the probe function in reverse
 * order.
 *
 * Notes:
 *
 *   The device may have been partially initialized, so double check
 *   that the allocations happened.
 *
 *   This function may be called multiple times, so mark resources as freed
 *   as they are deallocated.
 */
static void vsoc_remove_device(struct pci_dev *pdev)
{
	int i;
	/*
	 * pdev is the first thing to be set on probe and the last thing
	 * to be cleared here. If it's NULL then there is no cleanup.
	 */
	if (!pdev || !vsoc_dev.dev)
		return;
	dev_info(&pdev->dev, "remove_device\n");
	if (vsoc_dev.regions_data) {
		for (i = 0; i < vsoc_dev.layout->region_count; ++i) {
			if (vsoc_dev.regions_data[i].device_created) {
				device_destroy(vsoc_dev.class,
					       MKDEV(vsoc_dev.major, i));
				vsoc_dev.regions_data[i].device_created = false;
			}
			if (vsoc_dev.regions_data[i].irq_requested)
				free_irq(vsoc_dev.msix_entries[i].vector, NULL);
			vsoc_dev.regions_data[i].irq_requested = false;
		}
		kfree(vsoc_dev.regions_data);
		vsoc_dev.regions_data = NULL;
	}
	if (vsoc_dev.msix_enabled) {
		pci_disable_msix(pdev);
		vsoc_dev.msix_enabled = false;
	}
	kfree(vsoc_dev.msix_entries);
	vsoc_dev.msix_entries = NULL;
	vsoc_dev.regions = NULL;
	if (vsoc_dev.class_added) {
		class_destroy(vsoc_dev.class);
		vsoc_dev.class_added = false;
	}
	if (vsoc_dev.cdev_added) {
		cdev_del(&vsoc_dev.cdev);
		vsoc_dev.cdev_added = false;
	}
	if (vsoc_dev.major && vsoc_dev.layout) {
		unregister_chrdev_region(MKDEV(vsoc_dev.major, 0),
					 vsoc_dev.layout->region_count);
		vsoc_dev.major = 0;
	}
	vsoc_dev.layout = NULL;
	if (vsoc_dev.kernel_mapped_shm) {
		pci_iounmap(pdev, vsoc_dev.kernel_mapped_shm);
		vsoc_dev.kernel_mapped_shm = NULL;
	}
	if (vsoc_dev.regs) {
		pci_iounmap(pdev, vsoc_dev.regs);
		vsoc_dev.regs = NULL;
	}
	if (vsoc_dev.requested_regions) {
		pci_release_regions(pdev);
		vsoc_dev.requested_regions = false;
	}
	if (vsoc_dev.enabled_device) {
		pci_disable_device(pdev);
		vsoc_dev.enabled_device = false;
	}
	/* Do this last: it indicates that the device is not initialized. */
	vsoc_dev.dev = NULL;
}

static void __exit vsoc_cleanup_module(void)
{
	vsoc_remove_device(vsoc_dev.dev);
	pci_unregister_driver(&vsoc_pci_driver);
}

static int __init vsoc_init_module(void)
{
	int err = -ENOMEM;

	INIT_LIST_HEAD(&vsoc_dev.permissions);
	mutex_init(&vsoc_dev.mtx);

	err = pci_register_driver(&vsoc_pci_driver);
	if (err < 0)
		return err;
	return 0;
}

static int vsoc_open(struct inode *inode, struct file *filp)
{
	/* Can't use vsoc_validate_filep because filp is still incomplete */
	int ret = vsoc_validate_inode(inode);

	if (ret)
		return ret;
	filp->private_data =
		kzalloc(sizeof(struct vsoc_private_data), GFP_KERNEL);
	if (!filp->private_data)
		return -ENOMEM;
	return 0;
}

static int vsoc_release(struct inode *inode, struct file *filp)
{
	struct vsoc_private_data *private_data = NULL;
	struct fd_scoped_permission_node *node = NULL;
	struct vsoc_device_region *owner_region_p = NULL;
	int retval = vsoc_validate_filep(filp);

	if (retval)
		return retval;
	private_data = (struct vsoc_private_data *)filp->private_data;
	if (!private_data)
		return 0;

	node = private_data->fd_scoped_permission_node;
	if (node) {
		owner_region_p = vsoc_region_from_inode(inode);
		if (owner_region_p->managed_by != VSOC_REGION_WHOLE) {
			owner_region_p =
			    &vsoc_dev.regions[owner_region_p->managed_by];
		}
		do_destroy_fd_scoped_permission_node(owner_region_p, node);
		private_data->fd_scoped_permission_node = NULL;
	}
	kfree(private_data);
	filp->private_data = NULL;

	return 0;
}

/*
 * Returns the device relative offset and length of the area specified by the
 * fd scoped permission. If there is no fd scoped permission set, a default
 * permission covering the entire region is assumed, unless the region is owned
 * by another one, in which case the default is a permission with zero size.
 */
static ssize_t vsoc_get_area(struct file *filp, __u32 *area_offset)
{
	__u32 off = 0;
	ssize_t length = 0;
	struct vsoc_device_region *region_p;
	struct fd_scoped_permission *perm;

	region_p = vsoc_region_from_filep(filp);
	off = region_p->region_begin_offset;
	perm = &((struct vsoc_private_data *)filp->private_data)->
		fd_scoped_permission_node->permission;
	if (perm) {
		off += perm->begin_offset;
		length = perm->end_offset - perm->begin_offset;
	} else if (region_p->managed_by == VSOC_REGION_WHOLE) {
		/* No permission set and the regions is not owned by another,
		 * default to full region access.
		 */
		length = vsoc_device_region_size(region_p);
	} else {
		/* return zero length, access is denied. */
		length = 0;
	}
	if (area_offset)
		*area_offset = off;
	return length;
}

static int vsoc_mmap(struct file *filp, struct vm_area_struct *vma)
{
	unsigned long len = vma->vm_end - vma->vm_start;
	__u32 area_off;
	phys_addr_t mem_off;
	ssize_t area_len;
	int retval = vsoc_validate_filep(filp);

	if (retval)
		return retval;
	area_len = vsoc_get_area(filp, &area_off);
	/* Add the requested offset */
	area_off += (vma->vm_pgoff << PAGE_SHIFT);
	area_len -= (vma->vm_pgoff << PAGE_SHIFT);
	if (area_len < len)
		return -EINVAL;
	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
	mem_off = shm_off_to_phys_addr(area_off);
	if (io_remap_pfn_range(vma, vma->vm_start, mem_off >> PAGE_SHIFT,
			       len, vma->vm_page_prot))
		return -EAGAIN;
	return 0;
}

module_init(vsoc_init_module);
module_exit(vsoc_cleanup_module);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Greg Hartman <ghartman@google.com>");
MODULE_DESCRIPTION("VSoC interpretation of QEmu's ivshmem device");
MODULE_VERSION("1.0");