Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

[ARM] Fix virtual to physical translation macro corner cases

The current use of these macros works well when the conversion is
entirely linear. In this case, we can be assured that the following
holds true:

__va(p + s) - s = __va(p)

However, this is not always the case, especially when there is a
non-linear conversion (eg, when there is a 3.5GB hole in memory.)
In this case, if 's' is the size of the region (eg, PAGE_SIZE) and
'p' is the final page, the above is most definitely not true.

So, we must ensure that __va() and __pa() are only used with valid
kernel direct mapped RAM addresses. This patch tweaks the code
to achieve this.

Tested-by: Charles Moschel <fred99@carolina.rr.com>
Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>

authored by

Russell King and committed by
Russell King
1522ac3e 305b0768

+14 -10
+12 -8
arch/arm/mm/dma-mapping.c
··· 490 490 */ 491 491 void dma_cache_maint(const void *start, size_t size, int direction) 492 492 { 493 - const void *end = start + size; 493 + void (*inner_op)(const void *, const void *); 494 + void (*outer_op)(unsigned long, unsigned long); 494 495 495 - BUG_ON(!virt_addr_valid(start) || !virt_addr_valid(end - 1)); 496 + BUG_ON(!virt_addr_valid(start) || !virt_addr_valid(start + size - 1)); 496 497 497 498 switch (direction) { 498 499 case DMA_FROM_DEVICE: /* invalidate only */ 499 - dmac_inv_range(start, end); 500 - outer_inv_range(__pa(start), __pa(end)); 500 + inner_op = dmac_inv_range; 501 + outer_op = outer_inv_range; 501 502 break; 502 503 case DMA_TO_DEVICE: /* writeback only */ 503 - dmac_clean_range(start, end); 504 - outer_clean_range(__pa(start), __pa(end)); 504 + inner_op = dmac_clean_range; 505 + outer_op = outer_clean_range; 505 506 break; 506 507 case DMA_BIDIRECTIONAL: /* writeback and invalidate */ 507 - dmac_flush_range(start, end); 508 - outer_flush_range(__pa(start), __pa(end)); 508 + inner_op = dmac_flush_range; 509 + outer_op = outer_flush_range; 509 510 break; 510 511 default: 511 512 BUG(); 512 513 } 514 + 515 + inner_op(start, start + size); 516 + outer_op(__pa(start), __pa(start) + size); 513 517 } 514 518 EXPORT_SYMBOL(dma_cache_maint); 515 519
+1 -1
arch/arm/mm/init.c
··· 382 382 for_each_node(node) 383 383 bootmem_free_node(node, mi); 384 384 385 - high_memory = __va(memend_pfn << PAGE_SHIFT); 385 + high_memory = __va((memend_pfn << PAGE_SHIFT) - 1) + 1; 386 386 387 387 /* 388 388 * This doesn't seem to be used by the Linux memory manager any
+1 -1
arch/arm/mm/mmap.c
··· 124 124 { 125 125 if (addr < PHYS_OFFSET) 126 126 return 0; 127 - if (addr + size > __pa(high_memory)) 127 + if (addr + size >= __pa(high_memory - 1)) 128 128 return 0; 129 129 130 130 return 1;