[PATCH] powerpc: Implement dma_mmap_coherent()
Takashi Iwai
tiwai at suse.de
Fri Mar 25 19:06:31 EST 2011
At Fri, 25 Mar 2011 17:50:06 +1100,
Benjamin Herrenschmidt wrote:
>
> This is used by Alsa to mmap buffers allocated with dma_alloc_coherent()
> into userspace. We need a special variant to handle machines with
> non-coherent DMAs as those buffers have "special" virt addresses and
> require non-cachable mappings
>
> Signed-off-by: Benjamin Herrenschmidt <benh at kernel.crashing.org>
> ---
>
> Dunno if anybody with CONFIG_NOT_COHERENT_CACHE has some audio device
> that uses dma buffers (ie not usb-audio) and wants to try that out...
> should fix a long standing problem.
>
> arch/powerpc/include/asm/dma-mapping.h | 7 +++++++
> arch/powerpc/kernel/dma.c | 18 ++++++++++++++++++
> arch/powerpc/mm/dma-noncoherent.c | 20 ++++++++++++++++++++
> 3 files changed, 45 insertions(+), 0 deletions(-)
>
> diff --git a/arch/powerpc/include/asm/dma-mapping.h b/arch/powerpc/include/asm/dma-mapping.h
> index 6d2416a..41e0eb7 100644
> --- a/arch/powerpc/include/asm/dma-mapping.h
> +++ b/arch/powerpc/include/asm/dma-mapping.h
> @@ -42,6 +42,7 @@ extern void __dma_free_coherent(size_t size, void *vaddr);
> extern void __dma_sync(void *vaddr, size_t size, int direction);
> extern void __dma_sync_page(struct page *page, unsigned long offset,
> size_t size, int direction);
> +extern unsigned long __dma_get_coherent_pfn(void *cpu_addr);
>
> #else /* ! CONFIG_NOT_COHERENT_CACHE */
> /*
> @@ -52,6 +53,7 @@ extern void __dma_sync_page(struct page *page, unsigned long offset,
> #define __dma_free_coherent(size, addr) ((void)0)
> #define __dma_sync(addr, size, rw) ((void)0)
> #define __dma_sync_page(pg, off, sz, rw) ((void)0)
> +#define __dma_get_coherent_pfn(cpu_addr) (0)
Just nitpicking: shouldn't we define some real thing like
page_to_pfn(virt_to_page(cpu_addr))
or put something to trigger an error? Setting a bogus zero can be
easily overlooked, I'm afraid.
thanks,
Takashi
>
> #endif /* ! CONFIG_NOT_COHERENT_CACHE */
>
> @@ -198,6 +200,11 @@ static inline phys_addr_t dma_to_phys(struct device *dev, dma_addr_t daddr)
> #define dma_alloc_noncoherent(d, s, h, f) dma_alloc_coherent(d, s, h, f)
> #define dma_free_noncoherent(d, s, v, h) dma_free_coherent(d, s, v, h)
>
> +extern int dma_mmap_coherent(struct device *, struct vm_area_struct *,
> + void *, dma_addr_t, size_t);
> +#define ARCH_HAS_DMA_MMAP_COHERENT
> +
> +
> static inline void dma_cache_sync(struct device *dev, void *vaddr, size_t size,
> enum dma_data_direction direction)
> {
> diff --git a/arch/powerpc/kernel/dma.c b/arch/powerpc/kernel/dma.c
> index cf02cad..0f52235 100644
> --- a/arch/powerpc/kernel/dma.c
> +++ b/arch/powerpc/kernel/dma.c
> @@ -179,3 +179,21 @@ static int __init dma_init(void)
> return 0;
> }
> fs_initcall(dma_init);
> +
> +int dma_mmap_coherent(struct device *dev, struct vm_area_struct *vma,
> + void *cpu_addr, dma_addr_t handle, size_t size)
> +{
> + unsigned long pfn;
> +
> +#ifdef CONFIG_NOT_COHERENT_CACHE
> + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
> + pfn = __dma_get_coherent_pfn(cpu_addr);
> +#else
> + pfn = page_to_pfn(virt_to_page(cpu_addr));
> +#endif
> + return remap_pfn_range(vma, vma->vm_start,
> + pfn + vma->vm_pgoff,
> + vma->vm_end - vma->vm_start,
> + vma->vm_page_prot);
> +}
> +
> diff --git a/arch/powerpc/mm/dma-noncoherent.c b/arch/powerpc/mm/dma-noncoherent.c
> index 757c0be..174b10a 100644
> --- a/arch/powerpc/mm/dma-noncoherent.c
> +++ b/arch/powerpc/mm/dma-noncoherent.c
> @@ -399,3 +399,23 @@ void __dma_sync_page(struct page *page, unsigned long offset,
> #endif
> }
> EXPORT_SYMBOL(__dma_sync_page);
> +
> +/*
> + * Return the PFN for a given cpu virtual address returned by
> + * __dma_alloc_coherent. This is used by dma_mmap_coherent()
> + */
> +unsigned long __dma_get_coherent_pfn(void *cpu_addr)
> +{
> + /* This should always be populated, so we don't test every
> + * level. If that fails, we'll have a nice crash which
> + * will be as good as a BUG_ON()
> + */
> + pgd_t *pgd = pgd_offset_k(cpu_addr);
> + pud_t *pud = pud_offset(pgd, cpu_addr);
> + pmd_t *pmd = pmd_offset(pud, cpu_addr);
> + pte_t *ptep = pte_offset_kernel(pmd, cpu_addr);
> +
> + if (pte_none(*ptep) || !pte_present(*ptep))
> + return 0;
> + return pte_pfn(*ptep);
> +}
>
>
>
More information about the Linuxppc-dev
mailing list