summaryrefslogtreecommitdiff
path: root/drivers/comedi/comedi_fops.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/comedi/comedi_fops.c')
-rw-r--r--drivers/comedi/comedi_fops.c120
1 files changed, 86 insertions, 34 deletions
diff --git a/drivers/comedi/comedi_fops.c b/drivers/comedi/comedi_fops.c
index b9df9b19d4bd..3383a7ce27ff 100644
--- a/drivers/comedi/comedi_fops.c
+++ b/drivers/comedi/comedi_fops.c
@@ -2387,13 +2387,27 @@ static int comedi_mmap(struct file *file, struct vm_area_struct *vma)
goto done;
}
if (bm->dma_dir != DMA_NONE) {
+ unsigned long vm_start = vma->vm_start;
+ unsigned long vm_end = vma->vm_end;
+
/*
- * DMA buffer was allocated as a single block.
- * Address is in page_list[0].
+ * Buffer pages are not contiguous, so temporarily modify VMA
+ * start and end addresses for each buffer page.
*/
- buf = &bm->page_list[0];
- retval = dma_mmap_coherent(bm->dma_hw_dev, vma, buf->virt_addr,
- buf->dma_addr, n_pages * PAGE_SIZE);
+ for (i = 0; i < n_pages; ++i) {
+ buf = &bm->page_list[i];
+ vma->vm_start = start;
+ vma->vm_end = start + PAGE_SIZE;
+ retval = dma_mmap_coherent(bm->dma_hw_dev, vma,
+ buf->virt_addr,
+ buf->dma_addr, PAGE_SIZE);
+ if (retval)
+ break;
+
+ start += PAGE_SIZE;
+ }
+ vma->vm_start = vm_start;
+ vma->vm_end = vm_end;
} else {
for (i = 0; i < n_pages; ++i) {
unsigned long pfn;
@@ -2407,19 +2421,18 @@ static int comedi_mmap(struct file *file, struct vm_area_struct *vma)
start += PAGE_SIZE;
}
+ }
#ifdef CONFIG_MMU
- /*
- * Leaving behind a partial mapping of a buffer we're about to
- * drop is unsafe, see remap_pfn_range_notrack().
- * We need to zap the range here ourselves instead of relying
- * on the automatic zapping in remap_pfn_range() because we call
- * remap_pfn_range() in a loop.
- */
- if (retval)
- zap_vma_ptes(vma, vma->vm_start, size);
+ /*
+ * Leaving behind a partial mapping of a buffer we're about to drop is
+ * unsafe, see remap_pfn_range_notrack(). We need to zap the range
+ * here ourselves instead of relying on the automatic zapping in
+ * remap_pfn_range() because we call remap_pfn_range() in a loop.
+ */
+ if (retval)
+ zap_vma_ptes(vma, vma->vm_start, size);
#endif
- }
if (retval == 0) {
vma->vm_ops = &comedi_vm_ops;
@@ -2475,6 +2488,62 @@ done:
return mask;
}
+static unsigned int comedi_buf_copy_to_user(struct comedi_subdevice *s,
+ void __user *dest, unsigned int src_offset, unsigned int n)
+{
+ struct comedi_buf_map *bm = s->async->buf_map;
+ struct comedi_buf_page *buf_page_list = bm->page_list;
+ unsigned int page = src_offset >> PAGE_SHIFT;
+ unsigned int offset = offset_in_page(src_offset);
+
+ while (n) {
+ unsigned int copy_amount = min(n, PAGE_SIZE - offset);
+ unsigned int uncopied;
+
+ uncopied = copy_to_user(dest, buf_page_list[page].virt_addr +
+ offset, copy_amount);
+ copy_amount -= uncopied;
+ n -= copy_amount;
+ if (uncopied)
+ break;
+
+ dest += copy_amount;
+ page++;
+ if (page == bm->n_pages)
+ page = 0; /* buffer wraparound */
+ offset = 0;
+ }
+ return n;
+}
+
+static unsigned int comedi_buf_copy_from_user(struct comedi_subdevice *s,
+ unsigned int dst_offset, const void __user *src, unsigned int n)
+{
+ struct comedi_buf_map *bm = s->async->buf_map;
+ struct comedi_buf_page *buf_page_list = bm->page_list;
+ unsigned int page = dst_offset >> PAGE_SHIFT;
+ unsigned int offset = offset_in_page(dst_offset);
+
+ while (n) {
+ unsigned int copy_amount = min(n, PAGE_SIZE - offset);
+ unsigned int uncopied;
+
+ uncopied = copy_from_user(buf_page_list[page].virt_addr +
+ offset, src, copy_amount);
+ copy_amount -= uncopied;
+ n -= copy_amount;
+ if (uncopied)
+ break;
+
+ src += copy_amount;
+ page++;
+ if (page == bm->n_pages)
+ page = 0; /* buffer wraparound */
+ offset = 0;
+ }
+ return n;
+}
+
static ssize_t comedi_write(struct file *file, const char __user *buf,
size_t nbytes, loff_t *offset)
{
@@ -2516,7 +2585,6 @@ static ssize_t comedi_write(struct file *file, const char __user *buf,
add_wait_queue(&async->wait_head, &wait);
while (count == 0 && !retval) {
unsigned int runflags;
- unsigned int wp, n1, n2;
set_current_state(TASK_INTERRUPTIBLE);
@@ -2555,14 +2623,7 @@ static ssize_t comedi_write(struct file *file, const char __user *buf,
}
set_current_state(TASK_RUNNING);
- wp = async->buf_write_ptr;
- n1 = min(n, async->prealloc_bufsz - wp);
- n2 = n - n1;
- m = copy_from_user(async->prealloc_buf + wp, buf, n1);
- if (m)
- m += n2;
- else if (n2)
- m = copy_from_user(async->prealloc_buf, buf + n1, n2);
+ m = comedi_buf_copy_from_user(s, async->buf_write_ptr, buf, n);
if (m) {
n -= m;
retval = -EFAULT;
@@ -2651,8 +2712,6 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes,
add_wait_queue(&async->wait_head, &wait);
while (count == 0 && !retval) {
- unsigned int rp, n1, n2;
-
set_current_state(TASK_INTERRUPTIBLE);
m = comedi_buf_read_n_available(s);
@@ -2689,14 +2748,7 @@ static ssize_t comedi_read(struct file *file, char __user *buf, size_t nbytes,
}
set_current_state(TASK_RUNNING);
- rp = async->buf_read_ptr;
- n1 = min(n, async->prealloc_bufsz - rp);
- n2 = n - n1;
- m = copy_to_user(buf, async->prealloc_buf + rp, n1);
- if (m)
- m += n2;
- else if (n2)
- m = copy_to_user(buf + n1, async->prealloc_buf, n2);
+ m = comedi_buf_copy_to_user(s, buf, async->buf_read_ptr, n);
if (m) {
n -= m;
retval = -EFAULT;