Skip to content

Commit

Permalink
tilegx pci: support I/O to arbitrarily-cached pages
Browse files Browse the repository at this point in the history
The tilegx PCI root complex support (currently only in linux-next)
is limited to pages that are homed on cached in the default manner,
i.e. "hash-for-home".  This change supports delivery of I/O data to
pages that are cached in other ways (locally on a particular core,
uncached, user-managed incoherent, etc.).

A large part of the change is supporting flushing pages from cache
on particular homes so that we can transition the data that we are
delivering to or from the device appropriately.  The new homecache_finv*
routines handle this.

Some changes to page_table_range_init() were also required to make
the fixmap code work correctly on tilegx; it hadn't been used there
before.

We also remove some stub mark_caches_evicted_*() routines that
were just no-ops anyway.

Signed-off-by: Chris Metcalf <cmetcalf@tilera.com>
  • Loading branch information
Chris Metcalf committed Jul 18, 2012
1 parent 3e219b9 commit bbaa22c
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 171 deletions.
12 changes: 9 additions & 3 deletions arch/tile/include/asm/cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,17 @@
#define L2_CACHE_ALIGN(x) (((x)+(L2_CACHE_BYTES-1)) & -L2_CACHE_BYTES)

/*
* TILE-Gx is fully coherent so we don't need to define ARCH_DMA_MINALIGN.
* TILEPro I/O is not always coherent (networking typically uses coherent
* I/O, but PCI traffic does not) and setting ARCH_DMA_MINALIGN to the
* L2 cacheline size helps ensure that kernel heap allocations are aligned.
* TILE-Gx I/O is always coherent when used on hash-for-home pages.
*
* However, it's possible at runtime to request not to use hash-for-home
* for the kernel heap, in which case the kernel will use flush-and-inval
* to manage coherence. As a result, we use L2_CACHE_BYTES for the
* DMA minimum alignment to avoid false sharing in the kernel heap.
*/
#ifndef __tilegx__
#define ARCH_DMA_MINALIGN L2_CACHE_BYTES
#endif

/* use the cache line size for the L2, which is where it counts */
#define SMP_CACHE_BYTES_SHIFT L2_CACHE_SHIFT
Expand Down
14 changes: 11 additions & 3 deletions arch/tile/include/asm/fixmap.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,22 @@
*
* TLB entries of such buffers will not be flushed across
* task switches.
*
* We don't bother with a FIX_HOLE since above the fixmaps
* is unmapped memory in any case.
*/
enum fixed_addresses {
#ifdef __tilegx__
/*
* TILEPro has unmapped memory above so the hole isn't needed,
* and in any case the hole pushes us over a single 16MB pmd.
*/
FIX_HOLE,
#endif
#ifdef CONFIG_HIGHMEM
FIX_KMAP_BEGIN, /* reserved pte's for temporary kernel mappings */
FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1,
#endif
#ifdef __tilegx__ /* see homecache.c */
FIX_HOMECACHE_BEGIN,
FIX_HOMECACHE_END = FIX_HOMECACHE_BEGIN+(NR_CPUS)-1,
#endif
__end_of_permanent_fixed_addresses,

Expand Down
19 changes: 13 additions & 6 deletions arch/tile/include/asm/homecache.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,17 @@ extern void homecache_change_page_home(struct page *, int order, int home);
/*
* Flush a page out of whatever cache(s) it is in.
* This is more than just finv, since it properly handles waiting
* for the data to reach memory on tilepro, but it can be quite
* heavyweight, particularly on hash-for-home memory.
* for the data to reach memory, but it can be quite
* heavyweight, particularly on incoherent or immutable memory.
*/
extern void homecache_flush_cache(struct page *, int order);
extern void homecache_finv_page(struct page *);

/*
* Flush a page out of the specified home cache.
* Note that the specified home need not be the actual home of the page,
* as for example might be the case when coordinating with I/O devices.
*/
extern void homecache_finv_map_page(struct page *, int home);

/*
* Allocate a page with the given GFP flags, home, and optionally
Expand All @@ -104,10 +111,10 @@ extern struct page *homecache_alloc_pages_node(int nid, gfp_t gfp_mask,
* routines use homecache_change_page_home() to reset the home
* back to the default before returning the page to the allocator.
*/
void __homecache_free_pages(struct page *, unsigned int order);
void homecache_free_pages(unsigned long addr, unsigned int order);
#define homecache_free_page(page) \
homecache_free_pages((page), 0)

#define __homecache_free_page(page) __homecache_free_pages((page), 0)
#define homecache_free_page(page) homecache_free_pages((page), 0)


/*
Expand Down
7 changes: 3 additions & 4 deletions arch/tile/include/asm/page.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,9 @@ static inline __attribute_const__ int get_order(unsigned long size)
#define MEM_LOW_END (HALF_VA_SPACE - 1) /* low half */
#define MEM_HIGH_START (-HALF_VA_SPACE) /* high half */
#define PAGE_OFFSET MEM_HIGH_START
#define _VMALLOC_START _AC(0xfffffff500000000, UL) /* 4 GB */
#define FIXADDR_BASE _AC(0xfffffff400000000, UL) /* 4 GB */
#define FIXADDR_TOP _AC(0xfffffff500000000, UL) /* 4 GB */
#define _VMALLOC_START FIXADDR_TOP
#define HUGE_VMAP_BASE _AC(0xfffffff600000000, UL) /* 4 GB */
#define MEM_SV_START _AC(0xfffffff700000000, UL) /* 256 MB */
#define MEM_SV_INTRPT MEM_SV_START
Expand All @@ -185,9 +187,6 @@ static inline __attribute_const__ int get_order(unsigned long size)
/* Highest DTLB address we will use */
#define KERNEL_HIGH_VADDR MEM_SV_START

/* Since we don't currently provide any fixmaps, we use an impossible VA. */
#define FIXADDR_TOP MEM_HV_START

#else /* !__tilegx__ */

/*
Expand Down
182 changes: 143 additions & 39 deletions arch/tile/kernel/pci-dma.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,15 @@
/* Generic DMA mapping functions: */

/*
* Allocate what Linux calls "coherent" memory, which for us just
* means uncached.
* Allocate what Linux calls "coherent" memory. On TILEPro this is
* uncached memory; on TILE-Gx it is hash-for-home memory.
*/
#ifdef __tilepro__
#define PAGE_HOME_DMA PAGE_HOME_UNCACHED
#else
#define PAGE_HOME_DMA PAGE_HOME_HASH
#endif

void *dma_alloc_coherent(struct device *dev,
size_t size,
dma_addr_t *dma_handle,
Expand All @@ -48,13 +54,13 @@ void *dma_alloc_coherent(struct device *dev,
if (dma_mask <= DMA_BIT_MASK(32))
node = 0;

pg = homecache_alloc_pages_node(node, gfp, order, PAGE_HOME_UNCACHED);
pg = homecache_alloc_pages_node(node, gfp, order, PAGE_HOME_DMA);
if (pg == NULL)
return NULL;

addr = page_to_phys(pg);
if (addr + size > dma_mask) {
homecache_free_pages(addr, order);
__homecache_free_pages(pg, order);
return NULL;
}

Expand Down Expand Up @@ -87,22 +93,110 @@ EXPORT_SYMBOL(dma_free_coherent);
* can count on nothing having been touched.
*/

/* Flush a PA range from cache page by page. */
static void __dma_map_pa_range(dma_addr_t dma_addr, size_t size)
/* Set up a single page for DMA access. */
static void __dma_prep_page(struct page *page, unsigned long offset,
size_t size, enum dma_data_direction direction)
{
struct page *page = pfn_to_page(PFN_DOWN(dma_addr));
size_t bytesleft = PAGE_SIZE - (dma_addr & (PAGE_SIZE - 1));
/*
* Flush the page from cache if necessary.
* On tilegx, data is delivered to hash-for-home L3; on tilepro,
* data is delivered direct to memory.
*
* NOTE: If we were just doing DMA_TO_DEVICE we could optimize
* this to be a "flush" not a "finv" and keep some of the
* state in cache across the DMA operation, but it doesn't seem
* worth creating the necessary flush_buffer_xxx() infrastructure.
*/
int home = page_home(page);
switch (home) {
case PAGE_HOME_HASH:
#ifdef __tilegx__
return;
#endif
break;
case PAGE_HOME_UNCACHED:
#ifdef __tilepro__
return;
#endif
break;
case PAGE_HOME_IMMUTABLE:
/* Should be going to the device only. */
BUG_ON(direction == DMA_FROM_DEVICE ||
direction == DMA_BIDIRECTIONAL);
return;
case PAGE_HOME_INCOHERENT:
/* Incoherent anyway, so no need to work hard here. */
return;
default:
BUG_ON(home < 0 || home >= NR_CPUS);
break;
}
homecache_finv_page(page);

#ifdef DEBUG_ALIGNMENT
/* Warn if the region isn't cacheline aligned. */
if (offset & (L2_CACHE_BYTES - 1) || (size & (L2_CACHE_BYTES - 1)))
pr_warn("Unaligned DMA to non-hfh memory: PA %#llx/%#lx\n",
PFN_PHYS(page_to_pfn(page)) + offset, size);
#endif
}

while ((ssize_t)size > 0) {
/* Flush the page. */
homecache_flush_cache(page++, 0);
/* Make the page ready to be read by the core. */
static void __dma_complete_page(struct page *page, unsigned long offset,
size_t size, enum dma_data_direction direction)
{
#ifdef __tilegx__
switch (page_home(page)) {
case PAGE_HOME_HASH:
/* I/O device delivered data the way the cpu wanted it. */
break;
case PAGE_HOME_INCOHERENT:
/* Incoherent anyway, so no need to work hard here. */
break;
case PAGE_HOME_IMMUTABLE:
/* Extra read-only copies are not a problem. */
break;
default:
/* Flush the bogus hash-for-home I/O entries to memory. */
homecache_finv_map_page(page, PAGE_HOME_HASH);
break;
}
#endif
}

/* Figure out if we need to continue on the next page. */
size -= bytesleft;
bytesleft = PAGE_SIZE;
static void __dma_prep_pa_range(dma_addr_t dma_addr, size_t size,
enum dma_data_direction direction)
{
struct page *page = pfn_to_page(PFN_DOWN(dma_addr));
unsigned long offset = dma_addr & (PAGE_SIZE - 1);
size_t bytes = min(size, (size_t)(PAGE_SIZE - offset));

while (size != 0) {
__dma_prep_page(page, offset, bytes, direction);
size -= bytes;
++page;
offset = 0;
bytes = min((size_t)PAGE_SIZE, size);
}
}

static void __dma_complete_pa_range(dma_addr_t dma_addr, size_t size,
enum dma_data_direction direction)
{
struct page *page = pfn_to_page(PFN_DOWN(dma_addr));
unsigned long offset = dma_addr & (PAGE_SIZE - 1);
size_t bytes = min(size, (size_t)(PAGE_SIZE - offset));

while (size != 0) {
__dma_complete_page(page, offset, bytes, direction);
size -= bytes;
++page;
offset = 0;
bytes = min((size_t)PAGE_SIZE, size);
}
}


/*
* dma_map_single can be passed any memory address, and there appear
* to be no alignment constraints.
Expand All @@ -111,28 +205,29 @@ static void __dma_map_pa_range(dma_addr_t dma_addr, size_t size)
* line with some other data that has been touched in the meantime.
*/
dma_addr_t dma_map_single(struct device *dev, void *ptr, size_t size,
enum dma_data_direction direction)
enum dma_data_direction direction)
{
dma_addr_t dma_addr = __pa(ptr);

BUG_ON(!valid_dma_direction(direction));
WARN_ON(size == 0);

__dma_map_pa_range(dma_addr, size);
__dma_prep_pa_range(dma_addr, size, direction);

return dma_addr;
}
EXPORT_SYMBOL(dma_map_single);

void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size,
enum dma_data_direction direction)
enum dma_data_direction direction)
{
BUG_ON(!valid_dma_direction(direction));
__dma_complete_pa_range(dma_addr, size, direction);
}
EXPORT_SYMBOL(dma_unmap_single);

int dma_map_sg(struct device *dev, struct scatterlist *sglist, int nents,
enum dma_data_direction direction)
enum dma_data_direction direction)
{
struct scatterlist *sg;
int i;
Expand All @@ -143,17 +238,25 @@ int dma_map_sg(struct device *dev, struct scatterlist *sglist, int nents,

for_each_sg(sglist, sg, nents, i) {
sg->dma_address = sg_phys(sg);
__dma_map_pa_range(sg->dma_address, sg->length);
__dma_prep_pa_range(sg->dma_address, sg->length, direction);
}

return nents;
}
EXPORT_SYMBOL(dma_map_sg);

void dma_unmap_sg(struct device *dev, struct scatterlist *sg, int nhwentries,
enum dma_data_direction direction)
void dma_unmap_sg(struct device *dev, struct scatterlist *sglist, int nents,
enum dma_data_direction direction)
{
struct scatterlist *sg;
int i;

BUG_ON(!valid_dma_direction(direction));
for_each_sg(sglist, sg, nents, i) {
sg->dma_address = sg_phys(sg);
__dma_complete_pa_range(sg->dma_address, sg->length,
direction);
}
}
EXPORT_SYMBOL(dma_unmap_sg);

Expand All @@ -164,50 +267,51 @@ dma_addr_t dma_map_page(struct device *dev, struct page *page,
BUG_ON(!valid_dma_direction(direction));

BUG_ON(offset + size > PAGE_SIZE);
homecache_flush_cache(page, 0);

__dma_prep_page(page, offset, size, direction);
return page_to_pa(page) + offset;
}
EXPORT_SYMBOL(dma_map_page);

void dma_unmap_page(struct device *dev, dma_addr_t dma_address, size_t size,
enum dma_data_direction direction)
enum dma_data_direction direction)
{
BUG_ON(!valid_dma_direction(direction));
__dma_complete_page(pfn_to_page(PFN_DOWN(dma_address)),
dma_address & PAGE_OFFSET, size, direction);
}
EXPORT_SYMBOL(dma_unmap_page);

void dma_sync_single_for_cpu(struct device *dev, dma_addr_t dma_handle,
size_t size, enum dma_data_direction direction)
{
BUG_ON(!valid_dma_direction(direction));
__dma_complete_pa_range(dma_handle, size, direction);
}
EXPORT_SYMBOL(dma_sync_single_for_cpu);

void dma_sync_single_for_device(struct device *dev, dma_addr_t dma_handle,
size_t size, enum dma_data_direction direction)
{
unsigned long start = PFN_DOWN(dma_handle);
unsigned long end = PFN_DOWN(dma_handle + size - 1);
unsigned long i;

BUG_ON(!valid_dma_direction(direction));
for (i = start; i <= end; ++i)
homecache_flush_cache(pfn_to_page(i), 0);
__dma_prep_pa_range(dma_handle, size, direction);
}
EXPORT_SYMBOL(dma_sync_single_for_device);

void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sg, int nelems,
enum dma_data_direction direction)
void dma_sync_sg_for_cpu(struct device *dev, struct scatterlist *sglist,
int nelems, enum dma_data_direction direction)
{
struct scatterlist *sg;
int i;

BUG_ON(!valid_dma_direction(direction));
WARN_ON(nelems == 0 || sg[0].length == 0);
WARN_ON(nelems == 0 || sglist->length == 0);

for_each_sg(sglist, sg, nelems, i) {
dma_sync_single_for_cpu(dev, sg->dma_address,
sg_dma_len(sg), direction);
}
}
EXPORT_SYMBOL(dma_sync_sg_for_cpu);

/*
* Flush and invalidate cache for scatterlist.
*/
void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sglist,
int nelems, enum dma_data_direction direction)
{
Expand Down Expand Up @@ -242,8 +346,8 @@ void dma_sync_single_range_for_device(struct device *dev,
EXPORT_SYMBOL(dma_sync_single_range_for_device);

/*
* dma_alloc_noncoherent() returns non-cacheable memory, so there's no
* need to do any flushing here.
* dma_alloc_noncoherent() is #defined to return coherent memory,
* so there's no need to do any flushing here.
*/
void dma_cache_sync(struct device *dev, void *vaddr, size_t size,
enum dma_data_direction direction)
Expand Down
Loading

0 comments on commit bbaa22c

Please sign in to comment.