This patch introduces usage of dma_map_sg to map memory behind
a userspace pointer to a device as dma-contiguous mapping.
This patch contains some of the code kindly provided by Marek Szyprowski
m.szyprow...@samsung.com and Kamil Debski k.deb...@samsung.com and Andrzej
Pietrasiewicz andrze...@samsung.com. Kind thanks for bug reports from Laurent
Pinchart laurent.pinch...@ideasonboard.com and Seung-Woo Kim
sw0312@samsung.com.
Signed-off-by: Tomasz Stanislawski t.stanisl...@samsung.com
Signed-off-by: Kyungmin Park kyungmin.p...@samsung.com
Acked-by: Laurent Pinchart laurent.pinch...@ideasonboard.com
Acked-by: Hans Verkuil hans.verk...@cisco.com
---
drivers/media/v4l2-core/videobuf2-dma-contig.c | 226 ++--
1 file changed, 210 insertions(+), 16 deletions(-)
diff --git a/drivers/media/v4l2-core/videobuf2-dma-contig.c
b/drivers/media/v4l2-core/videobuf2-dma-contig.c
index daac2b2..8486e06 100644
--- a/drivers/media/v4l2-core/videobuf2-dma-contig.c
+++ b/drivers/media/v4l2-core/videobuf2-dma-contig.c
@@ -11,6 +11,8 @@
*/
#include linux/module.h
+#include linux/scatterlist.h
+#include linux/sched.h
#include linux/slab.h
#include linux/dma-mapping.h
@@ -27,6 +29,8 @@ struct vb2_dc_buf {
void*vaddr;
unsigned long size;
dma_addr_t dma_addr;
+ enum dma_data_direction dma_dir;
+ struct sg_table *dma_sgt;
/* MMAP related */
struct vb2_vmarea_handler handler;
@@ -37,6 +41,44 @@ struct vb2_dc_buf {
};
/*/
+/*scatterlist table functions*/
+/*/
+
+
+static void vb2_dc_sgt_foreach_page(struct sg_table *sgt,
+ void (*cb)(struct page *pg))
+{
+ struct scatterlist *s;
+ unsigned int i;
+
+ for_each_sg(sgt-sgl, s, sgt-orig_nents, i) {
+ struct page *page = sg_page(s);
+ unsigned int n_pages = PAGE_ALIGN(s-offset + s-length)
+PAGE_SHIFT;
+ unsigned int j;
+
+ for (j = 0; j n_pages; ++j, ++page)
+ cb(page);
+ }
+}
+
+static unsigned long vb2_dc_get_contiguous_size(struct sg_table *sgt)
+{
+ struct scatterlist *s;
+ dma_addr_t expected = sg_dma_address(sgt-sgl);
+ unsigned int i;
+ unsigned long size = 0;
+
+ for_each_sg(sgt-sgl, s, sgt-nents, i) {
+ if (sg_dma_address(s) != expected)
+ break;
+ expected = sg_dma_address(s) + sg_dma_len(s);
+ size += sg_dma_len(s);
+ }
+ return size;
+}
+
+/*/
/* callbacks for all buffers */
/*/
@@ -122,42 +164,194 @@ static int vb2_dc_mmap(void *buf_priv, struct
vm_area_struct *vma)
/* callbacks for USERPTR buffers */
/*/
+static inline int vma_is_io(struct vm_area_struct *vma)
+{
+ return !!(vma-vm_flags (VM_IO | VM_PFNMAP));
+}
+
+static int vb2_dc_get_user_pages(unsigned long start, struct page **pages,
+ int n_pages, struct vm_area_struct *vma, int write)
+{
+ if (vma_is_io(vma)) {
+ unsigned int i;
+
+ for (i = 0; i n_pages; ++i, start += PAGE_SIZE) {
+ unsigned long pfn;
+ int ret = follow_pfn(vma, start, pfn);
+
+ if (ret) {
+ pr_err(no page for address %lu\n, start);
+ return ret;
+ }
+ pages[i] = pfn_to_page(pfn);
+ }
+ } else {
+ int n;
+
+ n = get_user_pages(current, current-mm, start PAGE_MASK,
+ n_pages, write, 1, pages, NULL);
+ /* negative error means that no page was pinned */
+ n = max(n, 0);
+ if (n != n_pages) {
+ pr_err(got only %d of %d user pages\n, n, n_pages);
+ while (n)
+ put_page(pages[--n]);
+ return -EFAULT;
+ }
+ }
+
+ return 0;
+}
+
+static void vb2_dc_put_dirty_page(struct page *page)
+{
+ set_page_dirty_lock(page);
+ put_page(page);
+}
+
+static void vb2_dc_put_userptr(void *buf_priv)
+{
+ struct vb2_dc_buf *buf = buf_priv;
+ struct sg_table *sgt = buf-dma_sgt;
+
+ dma_unmap_sg(buf-dev, sgt-sgl, sgt-orig_nents, buf-dma_dir);
+ if (!vma_is_io(buf-vma))
+ vb2_dc_sgt_foreach_page(sgt, vb2_dc_put_dirty_page);
+
+ sg_free_table(sgt);
+ kfree(sgt);
+ vb2_put_vma(buf-vma);
+ kfree(buf);
+}
+
static void *vb2_dc_get_userptr(void *alloc_ctx, unsigned long vaddr,