diff options
author | Dmitry Kalinkin <dmitry.kalinkin@gmail.com> | 2015-02-26 18:53:10 +0300 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2015-03-07 04:03:22 +0300 |
commit | c74a804f115bdedcac72ea52ca33f46cfae3b74f (patch) | |
tree | 0e2c88fa7c1c0ba49147594f70d99688e9345c10 /drivers | |
parent | 0cd189a42da07c89c809debc1f6a75f5ec0f5c43 (diff) | |
download | linux-c74a804f115bdedcac72ea52ca33f46cfae3b74f.tar.xz |
staging: vme: mmap() support for vme_user
We also make sure that user won't be able to reconfigure the window while it is
mmap'ed.
Signed-off-by: Dmitry Kalinkin <dmitry.kalinkin@gmail.com>
Cc: Martyn Welch <martyn.welch@ge.com>
Cc: Igor Alekseev <igor.alekseev@itep.ru>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/staging/vme/devices/vme_user.c | 85 | ||||
-rw-r--r-- | drivers/vme/vme.c | 26 |
2 files changed, 111 insertions, 0 deletions
diff --git a/drivers/staging/vme/devices/vme_user.c b/drivers/staging/vme/devices/vme_user.c index 87318386034b..19ba749bb122 100644 --- a/drivers/staging/vme/devices/vme_user.c +++ b/drivers/staging/vme/devices/vme_user.c @@ -17,6 +17,7 @@ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt +#include <linux/atomic.h> #include <linux/cdev.h> #include <linux/delay.h> #include <linux/device.h> @@ -99,6 +100,7 @@ struct image_desc { struct device *device; /* Sysfs device */ struct vme_resource *resource; /* VME resource */ int users; /* Number of current users */ + int mmap_count; /* Number of current mmap's */ }; static struct image_desc image[VME_DEVS]; @@ -134,6 +136,10 @@ static ssize_t vme_user_write(struct file *, const char __user *, size_t, loff_t *); static loff_t vme_user_llseek(struct file *, loff_t, int); static long vme_user_unlocked_ioctl(struct file *, unsigned int, unsigned long); +static int vme_user_mmap(struct file *file, struct vm_area_struct *vma); + +static void vme_user_vm_open(struct vm_area_struct *vma); +static void vme_user_vm_close(struct vm_area_struct *vma); static int vme_user_match(struct vme_dev *); static int vme_user_probe(struct vme_dev *); @@ -147,6 +153,17 @@ static const struct file_operations vme_user_fops = { .llseek = vme_user_llseek, .unlocked_ioctl = vme_user_unlocked_ioctl, .compat_ioctl = vme_user_unlocked_ioctl, + .mmap = vme_user_mmap, +}; + +struct vme_user_vma_priv { + unsigned int minor; + atomic_t refcnt; +}; + +static const struct vm_operations_struct vme_user_vm_ops = { + .open = vme_user_vm_open, + .close = vme_user_vm_close, }; @@ -488,6 +505,11 @@ static int vme_user_ioctl(struct inode *inode, struct file *file, case VME_SET_MASTER: + if (image[minor].mmap_count != 0) { + pr_warn("Can't adjust mapped window\n"); + return -EPERM; + } + copied = copy_from_user(&master, argp, sizeof(master)); if (copied != 0) { pr_warn("Partial copy from userspace\n"); @@ -564,6 +586,69 @@ vme_user_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg) return ret; } +static void vme_user_vm_open(struct vm_area_struct *vma) +{ + struct vme_user_vma_priv *vma_priv = vma->vm_private_data; + + atomic_inc(&vma_priv->refcnt); +} + +static void vme_user_vm_close(struct vm_area_struct *vma) +{ + struct vme_user_vma_priv *vma_priv = vma->vm_private_data; + unsigned int minor = vma_priv->minor; + + if (!atomic_dec_and_test(&vma_priv->refcnt)) + return; + + mutex_lock(&image[minor].mutex); + image[minor].mmap_count--; + mutex_unlock(&image[minor].mutex); + + kfree(vma_priv); +} + +static int vme_user_master_mmap(unsigned int minor, struct vm_area_struct *vma) +{ + int err; + struct vme_user_vma_priv *vma_priv; + + mutex_lock(&image[minor].mutex); + + err = vme_master_mmap(image[minor].resource, vma); + if (err) { + mutex_unlock(&image[minor].mutex); + return err; + } + + vma_priv = kmalloc(sizeof(struct vme_user_vma_priv), GFP_KERNEL); + if (vma_priv == NULL) { + mutex_unlock(&image[minor].mutex); + return -ENOMEM; + } + + vma_priv->minor = minor; + atomic_set(&vma_priv->refcnt, 1); + vma->vm_ops = &vme_user_vm_ops; + vma->vm_private_data = vma_priv; + + image[minor].mmap_count++; + + mutex_unlock(&image[minor].mutex); + + return 0; +} + +static int vme_user_mmap(struct file *file, struct vm_area_struct *vma) +{ + unsigned int minor = MINOR(file_inode(file)->i_rdev); + + if (type[minor] == MASTER_MINOR) + return vme_user_master_mmap(minor, vma); + + return -ENODEV; +} + /* * Unallocate a previously allocated buffer diff --git a/drivers/vme/vme.c b/drivers/vme/vme.c index d95fb848dd03..6bab2c4ed77c 100644 --- a/drivers/vme/vme.c +++ b/drivers/vme/vme.c @@ -609,6 +609,32 @@ unsigned int vme_master_rmw(struct vme_resource *resource, unsigned int mask, } EXPORT_SYMBOL(vme_master_rmw); +int vme_master_mmap(struct vme_resource *resource, struct vm_area_struct *vma) +{ + struct vme_master_resource *image; + phys_addr_t phys_addr; + unsigned long vma_size; + + if (resource->type != VME_MASTER) { + pr_err("Not a master resource\n"); + return -EINVAL; + } + + image = list_entry(resource->entry, struct vme_master_resource, list); + phys_addr = image->bus_resource.start + (vma->vm_pgoff << PAGE_SHIFT); + vma_size = vma->vm_end - vma->vm_start; + + if (phys_addr + vma_size > image->bus_resource.end + 1) { + pr_err("Map size cannot exceed the window size\n"); + return -EFAULT; + } + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + return vm_iomap_memory(vma, phys_addr, vma->vm_end - vma->vm_start); +} +EXPORT_SYMBOL(vme_master_mmap); + void vme_master_free(struct vme_resource *resource) { struct vme_master_resource *master_image; |