Commit 774d2301 authored by Marek Szyprowski's avatar Marek Szyprowski Committed by Mauro Carvalho Chehab
Browse files

[media] videobuf2-dc: Fix support for mappings without struct page in userptr mode



Earlier version of dma-contig allocator in user ptr mode assumed that in
all cases DMA address equals physical address. This was just a special case.
Commit e15dab75 introduced correct support
for converting userpage to dma address, but unfortunately it broke the
support for simple dma address = physical address for the case, when given
physical frame has no struct page associated with it (this happens if one
use for example dma_declare_coherent api or other reserved memory approach).
This commit restores support for such cases.

Signed-off-by: default avatarMarek Szyprowski <m.szyprowski@samsung.com>
[s.nawrocki@samsung.com: replaced #elsif with #elif]
Signed-off-by: default avatarSylwester Nawrocki <s.nawrocki@samsung.com>

Signed-off-by: default avatarMauro Carvalho Chehab <m.chehab@samsung.com>
parent 3c5c23c5
Loading
Loading
Loading
Loading
+82 −5
Original line number Diff line number Diff line
@@ -423,6 +423,39 @@ 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_pfn(unsigned long start, int n_pages,
	struct vm_area_struct *vma, unsigned long *res)
{
	unsigned long pfn, start_pfn, prev_pfn;
	unsigned int i;
	int ret;

	if (!vma_is_io(vma))
		return -EFAULT;

	ret = follow_pfn(vma, start, &pfn);
	if (ret)
		return ret;

	start_pfn = pfn;
	start += PAGE_SIZE;

	for (i = 1; i < n_pages; ++i, start += PAGE_SIZE) {
		prev_pfn = pfn;
		ret = follow_pfn(vma, start, &pfn);

		if (ret) {
			pr_err("no page for address %lu\n", start);
			return ret;
		}
		if (pfn != prev_pfn + 1)
			return -EINVAL;
	}

	*res = start_pfn;
	return 0;
}

static int vb2_dc_get_user_pages(unsigned long start, struct page **pages,
	int n_pages, struct vm_area_struct *vma, int write)
{
@@ -433,6 +466,9 @@ static int vb2_dc_get_user_pages(unsigned long start, struct page **pages,
			unsigned long pfn;
			int ret = follow_pfn(vma, start, &pfn);

			if (!pfn_valid(pfn))
				return -EINVAL;

			if (ret) {
				pr_err("no page for address %lu\n", start);
				return ret;
@@ -468,16 +504,49 @@ static void vb2_dc_put_userptr(void *buf_priv)
	struct vb2_dc_buf *buf = buf_priv;
	struct sg_table *sgt = buf->dma_sgt;

	if (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);
}

/*
 * For some kind of reserved memory there might be no struct page available,
 * so all that can be done to support such 'pages' is to try to convert
 * pfn to dma address or at the last resort just assume that
 * dma address == physical address (like it has been assumed in earlier version
 * of videobuf2-dma-contig
 */

#ifdef __arch_pfn_to_dma
static inline dma_addr_t vb2_dc_pfn_to_dma(struct device *dev, unsigned long pfn)
{
	return (dma_addr_t)__arch_pfn_to_dma(dev, pfn);
}
#elif defined(__pfn_to_bus)
static inline dma_addr_t vb2_dc_pfn_to_dma(struct device *dev, unsigned long pfn)
{
	return (dma_addr_t)__pfn_to_bus(pfn);
}
#elif defined(__pfn_to_phys)
static inline dma_addr_t vb2_dc_pfn_to_dma(struct device *dev, unsigned long pfn)
{
	return (dma_addr_t)__pfn_to_phys(pfn);
}
#else
static inline dma_addr_t vb2_dc_pfn_to_dma(struct device *dev, unsigned long pfn)
{
	/* really, we cannot do anything better at this point */
	return (dma_addr_t)(pfn) << PAGE_SHIFT;
}
#endif

static void *vb2_dc_get_userptr(void *alloc_ctx, unsigned long vaddr,
	unsigned long size, int write)
{
@@ -548,6 +617,14 @@ static void *vb2_dc_get_userptr(void *alloc_ctx, unsigned long vaddr,
	/* extract page list from userspace mapping */
	ret = vb2_dc_get_user_pages(start, pages, n_pages, vma, write);
	if (ret) {
		unsigned long pfn;
		if (vb2_dc_get_user_pfn(start, n_pages, vma, &pfn) == 0) {
			buf->dma_addr = vb2_dc_pfn_to_dma(buf->dev, pfn);
			buf->size = size;
			kfree(pages);
			return buf;
		}

		pr_err("failed to get user pages\n");
		goto fail_vma;
	}