summaryrefslogtreecommitdiffstats
path: root/drivers/staging/comedi/comedi_fops.c
diff options
context:
space:
mode:
authorIan Abbott2013-11-08 16:03:43 +0100
committerGreg Kroah-Hartman2013-11-12 01:16:45 +0100
commitaf93da31634d6d55c9d313b5c49af8b272f7cb79 (patch)
tree43857c9d5c80ee5e3edf3ba916d0116da37df4e3 /drivers/staging/comedi/comedi_fops.c
parentstaging: comedi: make determination of read or write subdevice safer (diff)
downloadkernel-qcow2-linux-af93da31634d6d55c9d313b5c49af8b272f7cb79.tar.gz
kernel-qcow2-linux-af93da31634d6d55c9d313b5c49af8b272f7cb79.tar.xz
kernel-qcow2-linux-af93da31634d6d55c9d313b5c49af8b272f7cb79.zip
staging: comedi: protect buffer from being freed while mmapped
If a comedi device is automatically detached by `comedi_auto_unconfig()` any data buffers associated with subdevices that support asynchronous commands will be freed. If the buffer is mmapped at the time, bad things are likely to happen! Prevent this by moving some of the buffer details from `struct comedi_async` into a new, dynamically allocated, and kref-counted `struct comedi_buf_map`. This holds a list of pages, a reference count, and enough information to free the pages. The new member `buf_map` of `struct comedi_async` points to a `struct comedi_buf_map` when the buffer size is non-zero. Provide a new helper function `comedi_buf_is_mapped()` to check whether an a buffer is mmapped. If it is mmapped, the buffer is not allowed to be resized and the device is not allowed to be manually detached by the `COMEDI_DEVCONFIG` ioctl. Provide helper functions `comedi_buf_map_get()` and `comedi_buf_map_put()` to manipulate the reference count of the `struct comedi_buf_map`, which will be freed along with its contents via the 'release' callback of the `kref_put()` call. The reference count is manipulated by the vma operations and the mmap file operation. Now, when the comedi device is automatically detached, the buffer will be effectively freed by calling `comedi_buf_alloc()` with a new buffer size of 0. That calls local function `__comedi_buf_free()` which calls `comedi_buf_map_put()` on the `buf_map` member to free it. It won't actually be freed until the final 'put'. Signed-off-by: Ian Abbott <abbotti@mev.co.uk> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'drivers/staging/comedi/comedi_fops.c')
-rw-r--r--drivers/staging/comedi/comedi_fops.c38
1 files changed, 17 insertions, 21 deletions
diff --git a/drivers/staging/comedi/comedi_fops.c b/drivers/staging/comedi/comedi_fops.c
index 8cb9d956e8f2..58f2b27144ca 100644
--- a/drivers/staging/comedi/comedi_fops.c
+++ b/drivers/staging/comedi/comedi_fops.c
@@ -264,7 +264,7 @@ static int resize_async_buffer(struct comedi_device *dev,
DPRINTK("subdevice is busy, cannot resize buffer\n");
return -EBUSY;
}
- if (async->mmap_count) {
+ if (comedi_buf_is_mmapped(async)) {
DPRINTK("subdevice is mmapped, cannot resize buffer\n");
return -EBUSY;
}
@@ -647,7 +647,7 @@ static int is_device_busy(struct comedi_device *dev)
s = &dev->subdevices[i];
if (s->busy)
return 1;
- if (s->async && s->async->mmap_count)
+ if (s->async && comedi_buf_is_mmapped(s->async))
return 1;
}
@@ -1899,28 +1899,18 @@ done:
static void comedi_vm_open(struct vm_area_struct *area)
{
- struct comedi_async *async;
- struct comedi_device *dev;
+ struct comedi_buf_map *bm;
- async = area->vm_private_data;
- dev = async->subdevice->device;
-
- mutex_lock(&dev->mutex);
- async->mmap_count++;
- mutex_unlock(&dev->mutex);
+ bm = area->vm_private_data;
+ comedi_buf_map_get(bm);
}
static void comedi_vm_close(struct vm_area_struct *area)
{
- struct comedi_async *async;
- struct comedi_device *dev;
+ struct comedi_buf_map *bm;
- async = area->vm_private_data;
- dev = async->subdevice->device;
-
- mutex_lock(&dev->mutex);
- async->mmap_count--;
- mutex_unlock(&dev->mutex);
+ bm = area->vm_private_data;
+ comedi_buf_map_put(bm);
}
static struct vm_operations_struct comedi_vm_ops = {
@@ -1934,6 +1924,7 @@ static int comedi_mmap(struct file *file, struct vm_area_struct *vma)
struct comedi_device *dev = file->private_data;
struct comedi_subdevice *s;
struct comedi_async *async;
+ struct comedi_buf_map *bm;
unsigned long start = vma->vm_start;
unsigned long size;
int n_pages;
@@ -1980,8 +1971,13 @@ static int comedi_mmap(struct file *file, struct vm_area_struct *vma)
}
n_pages = size >> PAGE_SHIFT;
+ bm = async->buf_map;
+ if (!bm || n_pages > bm->n_pages) {
+ retval = -EINVAL;
+ goto done;
+ }
for (i = 0; i < n_pages; ++i) {
- struct comedi_buf_page *buf = &async->buf_page_list[i];
+ struct comedi_buf_page *buf = &bm->page_list[i];
if (remap_pfn_range(vma, start,
page_to_pfn(virt_to_page(buf->virt_addr)),
@@ -1993,9 +1989,9 @@ static int comedi_mmap(struct file *file, struct vm_area_struct *vma)
}
vma->vm_ops = &comedi_vm_ops;
- vma->vm_private_data = async;
+ vma->vm_private_data = bm;
- async->mmap_count++;
+ vma->vm_ops->open(vma);
retval = 0;
done: