diff options
Diffstat (limited to 'arch/arm/mm/dma-mapping.c')
-rw-r--r-- | arch/arm/mm/dma-mapping.c | 92 |
1 files changed, 83 insertions, 9 deletions
diff --git a/arch/arm/mm/dma-mapping.c b/arch/arm/mm/dma-mapping.c index 310e479309ef..510c179b0ac8 100644 --- a/arch/arm/mm/dma-mapping.c +++ b/arch/arm/mm/dma-mapping.c | |||
@@ -19,6 +19,7 @@ | |||
19 | #include <linux/dma-mapping.h> | 19 | #include <linux/dma-mapping.h> |
20 | 20 | ||
21 | #include <asm/memory.h> | 21 | #include <asm/memory.h> |
22 | #include <asm/highmem.h> | ||
22 | #include <asm/cacheflush.h> | 23 | #include <asm/cacheflush.h> |
23 | #include <asm/tlbflush.h> | 24 | #include <asm/tlbflush.h> |
24 | #include <asm/sizes.h> | 25 | #include <asm/sizes.h> |
@@ -490,29 +491,101 @@ core_initcall(consistent_init); | |||
490 | */ | 491 | */ |
491 | void dma_cache_maint(const void *start, size_t size, int direction) | 492 | void dma_cache_maint(const void *start, size_t size, int direction) |
492 | { | 493 | { |
493 | const void *end = start + size; | 494 | void (*inner_op)(const void *, const void *); |
495 | void (*outer_op)(unsigned long, unsigned long); | ||
494 | 496 | ||
495 | BUG_ON(!virt_addr_valid(start) || !virt_addr_valid(end - 1)); | 497 | BUG_ON(!virt_addr_valid(start) || !virt_addr_valid(start + size - 1)); |
496 | 498 | ||
497 | switch (direction) { | 499 | switch (direction) { |
498 | case DMA_FROM_DEVICE: /* invalidate only */ | 500 | case DMA_FROM_DEVICE: /* invalidate only */ |
499 | dmac_inv_range(start, end); | 501 | inner_op = dmac_inv_range; |
500 | outer_inv_range(__pa(start), __pa(end)); | 502 | outer_op = outer_inv_range; |
501 | break; | 503 | break; |
502 | case DMA_TO_DEVICE: /* writeback only */ | 504 | case DMA_TO_DEVICE: /* writeback only */ |
503 | dmac_clean_range(start, end); | 505 | inner_op = dmac_clean_range; |
504 | outer_clean_range(__pa(start), __pa(end)); | 506 | outer_op = outer_clean_range; |
505 | break; | 507 | break; |
506 | case DMA_BIDIRECTIONAL: /* writeback and invalidate */ | 508 | case DMA_BIDIRECTIONAL: /* writeback and invalidate */ |
507 | dmac_flush_range(start, end); | 509 | inner_op = dmac_flush_range; |
508 | outer_flush_range(__pa(start), __pa(end)); | 510 | outer_op = outer_flush_range; |
509 | break; | 511 | break; |
510 | default: | 512 | default: |
511 | BUG(); | 513 | BUG(); |
512 | } | 514 | } |
515 | |||
516 | inner_op(start, start + size); | ||
517 | outer_op(__pa(start), __pa(start) + size); | ||
513 | } | 518 | } |
514 | EXPORT_SYMBOL(dma_cache_maint); | 519 | EXPORT_SYMBOL(dma_cache_maint); |
515 | 520 | ||
521 | static void dma_cache_maint_contiguous(struct page *page, unsigned long offset, | ||
522 | size_t size, int direction) | ||
523 | { | ||
524 | void *vaddr; | ||
525 | unsigned long paddr; | ||
526 | void (*inner_op)(const void *, const void *); | ||
527 | void (*outer_op)(unsigned long, unsigned long); | ||
528 | |||
529 | switch (direction) { | ||
530 | case DMA_FROM_DEVICE: /* invalidate only */ | ||
531 | inner_op = dmac_inv_range; | ||
532 | outer_op = outer_inv_range; | ||
533 | break; | ||
534 | case DMA_TO_DEVICE: /* writeback only */ | ||
535 | inner_op = dmac_clean_range; | ||
536 | outer_op = outer_clean_range; | ||
537 | break; | ||
538 | case DMA_BIDIRECTIONAL: /* writeback and invalidate */ | ||
539 | inner_op = dmac_flush_range; | ||
540 | outer_op = outer_flush_range; | ||
541 | break; | ||
542 | default: | ||
543 | BUG(); | ||
544 | } | ||
545 | |||
546 | if (!PageHighMem(page)) { | ||
547 | vaddr = page_address(page) + offset; | ||
548 | inner_op(vaddr, vaddr + size); | ||
549 | } else { | ||
550 | vaddr = kmap_high_get(page); | ||
551 | if (vaddr) { | ||
552 | vaddr += offset; | ||
553 | inner_op(vaddr, vaddr + size); | ||
554 | kunmap_high(page); | ||
555 | } | ||
556 | } | ||
557 | |||
558 | paddr = page_to_phys(page) + offset; | ||
559 | outer_op(paddr, paddr + size); | ||
560 | } | ||
561 | |||
562 | void dma_cache_maint_page(struct page *page, unsigned long offset, | ||
563 | size_t size, int dir) | ||
564 | { | ||
565 | /* | ||
566 | * A single sg entry may refer to multiple physically contiguous | ||
567 | * pages. But we still need to process highmem pages individually. | ||
568 | * If highmem is not configured then the bulk of this loop gets | ||
569 | * optimized out. | ||
570 | */ | ||
571 | size_t left = size; | ||
572 | do { | ||
573 | size_t len = left; | ||
574 | if (PageHighMem(page) && len + offset > PAGE_SIZE) { | ||
575 | if (offset >= PAGE_SIZE) { | ||
576 | page += offset / PAGE_SIZE; | ||
577 | offset %= PAGE_SIZE; | ||
578 | } | ||
579 | len = PAGE_SIZE - offset; | ||
580 | } | ||
581 | dma_cache_maint_contiguous(page, offset, len, dir); | ||
582 | offset = 0; | ||
583 | page++; | ||
584 | left -= len; | ||
585 | } while (left); | ||
586 | } | ||
587 | EXPORT_SYMBOL(dma_cache_maint_page); | ||
588 | |||
516 | /** | 589 | /** |
517 | * dma_map_sg - map a set of SG buffers for streaming mode DMA | 590 | * dma_map_sg - map a set of SG buffers for streaming mode DMA |
518 | * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices | 591 | * @dev: valid struct device pointer, or NULL for ISA and EISA-like devices |
@@ -610,7 +683,8 @@ void dma_sync_sg_for_device(struct device *dev, struct scatterlist *sg, | |||
610 | continue; | 683 | continue; |
611 | 684 | ||
612 | if (!arch_is_coherent()) | 685 | if (!arch_is_coherent()) |
613 | dma_cache_maint(sg_virt(s), s->length, dir); | 686 | dma_cache_maint_page(sg_page(s), s->offset, |
687 | s->length, dir); | ||
614 | } | 688 | } |
615 | } | 689 | } |
616 | EXPORT_SYMBOL(dma_sync_sg_for_device); | 690 | EXPORT_SYMBOL(dma_sync_sg_for_device); |