Skip to content

Commit

Permalink
powerpc: Fix up dma_alloc_coherent() on platforms without cache coher…
Browse files Browse the repository at this point in the history
…ency.

The implementation we just revived has issues, such as using a
Kconfig-defined virtual address area in kernel space that nothing
actually carves out (and thus will overlap whatever is there),
or having some dependencies on being self contained in a single
PTE page which adds unnecessary constraints on the kernel virtual
address space.

This fixes it by using more classic PTE accessors and automatically
locating the area for consistent memory, carving an appropriate hole
in the kernel virtual address space, leaving only the size of that
area as a Kconfig option. It also brings some dma-mask related fixes
from the ARM implementation which was almost identical initially but
grew its own fixes.

Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
  • Loading branch information
Benjamin Herrenschmidt committed May 27, 2009
1 parent f637a49 commit 8b31e49
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 83 deletions.
13 changes: 0 additions & 13 deletions arch/powerpc/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -868,19 +868,6 @@ config TASK_SIZE
default "0x80000000" if PPC_PREP || PPC_8xx
default "0xc0000000"

config CONSISTENT_START_BOOL
bool "Set custom consistent memory pool address"
depends on ADVANCED_OPTIONS && NOT_COHERENT_CACHE
help
This option allows you to set the base virtual address
of the consistent memory pool. This pool of virtual
memory is used to make consistent memory allocations.

config CONSISTENT_START
hex "Base virtual address of consistent memory pool" if CONSISTENT_START_BOOL
default "0xfd000000" if (NOT_COHERENT_CACHE && 8xx)
default "0xff100000" if NOT_COHERENT_CACHE

config CONSISTENT_SIZE_BOOL
bool "Set custom consistent memory pool size"
depends on ADVANCED_OPTIONS && NOT_COHERENT_CACHE
Expand Down
6 changes: 4 additions & 2 deletions arch/powerpc/include/asm/dma-mapping.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
* allocate the space "normally" and use the cache management functions
* to ensure it is consistent.
*/
extern void *__dma_alloc_coherent(size_t size, dma_addr_t *handle, gfp_t gfp);
struct device;
extern void *__dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *handle, gfp_t gfp);
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,
Expand All @@ -37,7 +39,7 @@ extern void __dma_sync_page(struct page *page, unsigned long offset,
* Cache coherent cores.
*/

#define __dma_alloc_coherent(gfp, size, handle) NULL
#define __dma_alloc_coherent(dev, gfp, size, handle) NULL
#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)
Expand Down
4 changes: 4 additions & 0 deletions arch/powerpc/include/asm/pgtable-ppc32.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,11 @@ extern int icache_44x_need_flush;
* until mem_init() at which point this becomes the top of the vmalloc
* and ioremap space
*/
#ifdef CONFIG_NOT_COHERENT_CACHE
#define IOREMAP_TOP ((KVIRT_TOP - CONFIG_CONSISTENT_SIZE) & PAGE_MASK)
#else
#define IOREMAP_TOP KVIRT_TOP
#endif

/*
* Just any arbitrary offset to the start of the vmalloc VM area: the
Expand Down
2 changes: 1 addition & 1 deletion arch/powerpc/kernel/dma.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ void *dma_direct_alloc_coherent(struct device *dev, size_t size,
{
void *ret;
#ifdef CONFIG_NOT_COHERENT_CACHE
ret = __dma_alloc_coherent(size, dma_handle, flag);
ret = __dma_alloc_coherent(dev, size, dma_handle, flag);
if (ret == NULL)
return NULL;
*dma_handle += get_dma_direct_offset(dev);
Expand Down
108 changes: 41 additions & 67 deletions arch/powerpc/mm/dma-noncoherent.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,21 @@

#include <asm/tlbflush.h>

#include "mmu_decl.h"

/*
* This address range defaults to a value that is safe for all
* platforms which currently set CONFIG_NOT_COHERENT_CACHE. It
* can be further configured for specific applications under
* the "Advanced Setup" menu. -Matt
*/
#define CONSISTENT_BASE (CONFIG_CONSISTENT_START)
#define CONSISTENT_END (CONFIG_CONSISTENT_START + CONFIG_CONSISTENT_SIZE)
#define CONSISTENT_BASE (IOREMAP_TOP)
#define CONSISTENT_END (CONSISTENT_BASE + CONFIG_CONSISTENT_SIZE)
#define CONSISTENT_OFFSET(x) (((unsigned long)(x) - CONSISTENT_BASE) >> PAGE_SHIFT)

/*
* This is the page table (2MB) covering uncached, DMA consistent allocations
*/
static pte_t *consistent_pte;
static DEFINE_SPINLOCK(consistent_lock);

/*
Expand Down Expand Up @@ -148,29 +149,46 @@ static struct ppc_vm_region *ppc_vm_region_find(struct ppc_vm_region *head, unsi
* virtual and bus address for that space.
*/
void *
__dma_alloc_coherent(size_t size, dma_addr_t *handle, gfp_t gfp)
__dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *handle, gfp_t gfp)
{
struct page *page;
struct ppc_vm_region *c;
unsigned long order;
u64 mask = 0x00ffffff, limit; /* ISA default */
u64 mask = ISA_DMA_THRESHOLD, limit;

if (!consistent_pte) {
printk(KERN_ERR "%s: not initialised\n", __func__);
dump_stack();
return NULL;
if (dev) {
mask = dev->coherent_dma_mask;

/*
* Sanity check the DMA mask - it must be non-zero, and
* must be able to be satisfied by a DMA allocation.
*/
if (mask == 0) {
dev_warn(dev, "coherent DMA mask is unset\n");
goto no_page;
}

if ((~mask) & ISA_DMA_THRESHOLD) {
dev_warn(dev, "coherent DMA mask %#llx is smaller "
"than system GFP_DMA mask %#llx\n",
mask, (unsigned long long)ISA_DMA_THRESHOLD);
goto no_page;
}
}


size = PAGE_ALIGN(size);
limit = (mask + 1) & ~mask;
if ((limit && size >= limit) || size >= (CONSISTENT_END - CONSISTENT_BASE)) {
if ((limit && size >= limit) ||
size >= (CONSISTENT_END - CONSISTENT_BASE)) {
printk(KERN_WARNING "coherent allocation too big (requested %#x mask %#Lx)\n",
size, mask);
return NULL;
}

order = get_order(size);

/* Might be useful if we ever have a real legacy DMA zone... */
if (mask != 0xffffffff)
gfp |= GFP_DMA;

Expand All @@ -195,7 +213,6 @@ __dma_alloc_coherent(size_t size, dma_addr_t *handle, gfp_t gfp)
gfp & ~(__GFP_DMA | __GFP_HIGHMEM));
if (c) {
unsigned long vaddr = c->vm_start;
pte_t *pte = consistent_pte + CONSISTENT_OFFSET(vaddr);
struct page *end = page + (1 << order);

split_page(page, order);
Expand All @@ -206,13 +223,10 @@ __dma_alloc_coherent(size_t size, dma_addr_t *handle, gfp_t gfp)
*handle = page_to_phys(page);

do {
BUG_ON(!pte_none(*pte));

SetPageReserved(page);
set_pte_at(&init_mm, vaddr,
pte, mk_pte(page, pgprot_noncached(PAGE_KERNEL)));
map_page(vaddr, page_to_phys(page),
pgprot_noncached(PAGE_KERNEL));
page++;
pte++;
vaddr += PAGE_SIZE;
} while (size -= PAGE_SIZE);

Expand Down Expand Up @@ -241,8 +255,7 @@ void __dma_free_coherent(size_t size, void *vaddr)
{
struct ppc_vm_region *c;
unsigned long flags, addr;
pte_t *ptep;


size = PAGE_ALIGN(size);

spin_lock_irqsave(&consistent_lock, flags);
Expand All @@ -258,29 +271,26 @@ void __dma_free_coherent(size_t size, void *vaddr)
size = c->vm_end - c->vm_start;
}

ptep = consistent_pte + CONSISTENT_OFFSET(c->vm_start);
addr = c->vm_start;
do {
pte_t pte = ptep_get_and_clear(&init_mm, addr, ptep);
pte_t *ptep;
unsigned long pfn;

ptep++;
addr += PAGE_SIZE;

if (!pte_none(pte) && pte_present(pte)) {
pfn = pte_pfn(pte);

ptep = pte_offset_kernel(pmd_offset(pud_offset(pgd_offset_k(addr),
addr),
addr),
addr);
if (!pte_none(*ptep) && pte_present(*ptep)) {
pfn = pte_pfn(*ptep);
pte_clear(&init_mm, addr, ptep);
if (pfn_valid(pfn)) {
struct page *page = pfn_to_page(pfn);
ClearPageReserved(page);

ClearPageReserved(page);
__free_page(page);
continue;
}
}

printk(KERN_CRIT "%s: bad page in kernel page table\n",
__func__);
addr += PAGE_SIZE;
} while (size -= PAGE_SIZE);

flush_tlb_kernel_range(c->vm_start, c->vm_end);
Expand All @@ -300,42 +310,6 @@ void __dma_free_coherent(size_t size, void *vaddr)
}
EXPORT_SYMBOL(__dma_free_coherent);

/*
* Initialise the consistent memory allocation.
*/
static int __init dma_alloc_init(void)
{
pgd_t *pgd;
pud_t *pud;
pmd_t *pmd;
pte_t *pte;
int ret = 0;

do {
pgd = pgd_offset(&init_mm, CONSISTENT_BASE);
pud = pud_alloc(&init_mm, pgd, CONSISTENT_BASE);
pmd = pmd_alloc(&init_mm, pud, CONSISTENT_BASE);
if (!pmd) {
printk(KERN_ERR "%s: no pmd tables\n", __func__);
ret = -ENOMEM;
break;
}

pte = pte_alloc_kernel(pmd, CONSISTENT_BASE);
if (!pte) {
printk(KERN_ERR "%s: no pte tables\n", __func__);
ret = -ENOMEM;
break;
}

consistent_pte = pte;
} while (0);

return ret;
}

core_initcall(dma_alloc_init);

/*
* make an area consistent.
*/
Expand Down
4 changes: 4 additions & 0 deletions arch/powerpc/mm/mem.c
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,10 @@ void __init mem_init(void)
pr_info(" * 0x%08lx..0x%08lx : highmem PTEs\n",
PKMAP_BASE, PKMAP_ADDR(LAST_PKMAP));
#endif /* CONFIG_HIGHMEM */
#ifdef CONFIG_NOT_COHERENT_CACHE
pr_info(" * 0x%08lx..0x%08lx : consistent mem\n",
IOREMAP_TOP, IOREMAP_TOP + CONFIG_CONSISTENT_SIZE);
#endif /* CONFIG_NOT_COHERENT_CACHE */
pr_info(" * 0x%08lx..0x%08lx : early ioremap\n",
ioremap_bot, IOREMAP_TOP);
pr_info(" * 0x%08lx..0x%08lx : vmalloc & ioremap\n",
Expand Down

0 comments on commit 8b31e49

Please sign in to comment.